1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.LayoutTransition;
24 import android.animation.ObjectAnimator;
25 import android.animation.PropertyValuesHolder;
26 import android.animation.TimeInterpolator;
27 import android.animation.ValueAnimator;
28 import android.animation.ValueAnimator.AnimatorUpdateListener;
29 import android.app.WallpaperManager;
30 import android.appwidget.AppWidgetHostView;
31 import android.appwidget.AppWidgetProviderInfo;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.SharedPreferences;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.res.Resources;
39 import android.content.res.TypedArray;
40 import android.graphics.Bitmap;
41 import android.graphics.Canvas;
42 import android.graphics.Matrix;
43 import android.graphics.Paint;
44 import android.graphics.Point;
45 import android.graphics.PointF;
46 import android.graphics.Rect;
47 import android.graphics.Region.Op;
48 import android.graphics.drawable.Drawable;
49 import android.net.Uri;
50 import android.os.AsyncTask;
51 import android.os.Handler;
52 import android.os.IBinder;
53 import android.os.Parcelable;
54 import android.support.v4.view.ViewCompat;
55 import android.util.AttributeSet;
56 import android.util.Log;
57 import android.util.SparseArray;
58 import android.view.Choreographer;
59 import android.view.Display;
60 import android.view.MotionEvent;
61 import android.view.View;
62 import android.view.ViewGroup;
63 import android.view.accessibility.AccessibilityManager;
64 import android.view.animation.DecelerateInterpolator;
65 import android.view.animation.Interpolator;
66 import android.widget.TextView;
67
68 import com.android.launcher3.FolderIcon.FolderRingAnimator;
69 import com.android.launcher3.Launcher.CustomContentCallbacks;
70 import com.android.launcher3.LauncherSettings.Favorites;
71 import com.android.launcher3.compat.PackageInstallerCompat;
72 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
73 import com.android.launcher3.compat.UserHandleCompat;
74
75 import java.util.ArrayList;
76 import java.util.HashMap;
77 import java.util.HashSet;
78 import java.util.Iterator;
79 import java.util.Map;
80 import java.util.Set;
81 import java.util.concurrent.atomic.AtomicInteger;
82
83 /**
84 * The workspace is a wide area with a wallpaper and a finite number of pages.
85 * Each page contains a number of icons, folders or widgets the user can
86 * interact with. A workspace is meant to be used with a fixed width only.
87 */
88 public class Workspace extends SmoothPagedView
89 implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
90 DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
91 Insettable {
92 private static final String TAG = "Launcher.Workspace";
93
94 // Y rotation to apply to the workspace screens
95 private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
96
97 private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
98 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
99 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
100
101 protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
102 protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
103
104 private static final int BACKGROUND_FADE_OUT_DURATION = 350;
105 private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
106 private static final int FLING_THRESHOLD_VELOCITY = 500;
107
108 private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
109
110 static final boolean MAP_NO_RECURSE = false;
111 static final boolean MAP_RECURSE = true;
112
113 // These animators are used to fade the children's outlines
114 private ObjectAnimator mChildrenOutlineFadeInAnimation;
115 private ObjectAnimator mChildrenOutlineFadeOutAnimation;
116 private float mChildrenOutlineAlpha = 0;
117
118 // These properties refer to the background protection gradient used for AllApps and Customize
119 private ValueAnimator mBackgroundFadeInAnimation;
120 private ValueAnimator mBackgroundFadeOutAnimation;
121
122 private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
123 private long mTouchDownTime = -1;
124 private long mCustomContentShowTime = -1;
125
126 private LayoutTransition mLayoutTransition;
127 private final WallpaperManager mWallpaperManager;
128 private IBinder mWindowToken;
129
130 private int mOriginalDefaultPage;
131 private int mDefaultPage;
132
133 private ShortcutAndWidgetContainer mDragSourceInternal;
134 private static boolean sAccessibilityEnabled;
135
136 // The screen id used for the empty screen always present to the right.
137 final static long EXTRA_EMPTY_SCREEN_ID = -201;
138 private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
139
140 private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
141 private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
142
143 private Runnable mRemoveEmptyScreenRunnable;
144 private boolean mDeferRemoveExtraEmptyScreen = false;
145
146 /**
147 * CellInfo for the cell that is currently being dragged
148 */
149 private CellLayout.CellInfo mDragInfo;
150
151 /**
152 * Target drop area calculated during last acceptDrop call.
153 */
154 private int[] mTargetCell = new int[2];
155 private int mDragOverX = -1;
156 private int mDragOverY = -1;
157
158 static Rect mLandscapeCellLayoutMetrics = null;
159 static Rect mPortraitCellLayoutMetrics = null;
160
161 CustomContentCallbacks mCustomContentCallbacks;
162 boolean mCustomContentShowing;
163 private float mLastCustomContentScrollProgress = -1f;
164 private String mCustomContentDescription = "";
165
166 /**
167 * The CellLayout that is currently being dragged over
168 */
169 private CellLayout mDragTargetLayout = null;
170 /**
171 * The CellLayout that we will show as glowing
172 */
173 private CellLayout mDragOverlappingLayout = null;
174
175 /**
176 * The CellLayout which will be dropped to
177 */
178 private CellLayout mDropToLayout = null;
179
180 private Launcher mLauncher;
181 private IconCache mIconCache;
182 private DragController mDragController;
183
184 // These are temporary variables to prevent having to allocate a new object just to
185 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
186 private int[] mTempCell = new int[2];
187 private int[] mTempPt = new int[2];
188 private int[] mTempEstimate = new int[2];
189 private float[] mDragViewVisualCenter = new float[2];
190 private float[] mTempCellLayoutCenterCoordinates = new float[2];
191 private Matrix mTempInverseMatrix = new Matrix();
192
193 private SpringLoadedDragController mSpringLoadedDragController;
194 private float mSpringLoadedShrinkFactor;
195 private float mOverviewModeShrinkFactor;
196
197 // State variable that indicates whether the pages are small (ie when you're
198 // in all apps or customize mode)
199
200 enum State { NORMAL, NORMAL_HIDDEN, SPRING_LOADED, OVERVIEW, OVERVIEW_HIDDEN};
201 private State mState = State.NORMAL;
202 private boolean mIsSwitchingState = false;
203
204 boolean mAnimatingViewIntoPlace = false;
205 boolean mIsDragOccuring = false;
206 boolean mChildrenLayersEnabled = true;
207
208 private boolean mStripScreensOnPageStopMoving = false;
209
210 /** Is the user is dragging an item near the edge of a page? */
211 private boolean mInScrollArea = false;
212
213 private HolographicOutlineHelper mOutlineHelper;
214 private Bitmap mDragOutline = null;
215 private static final Rect sTempRect = new Rect();
216 private final int[] mTempXY = new int[2];
217 private int[] mTempVisiblePagesRange = new int[2];
218 private boolean mOverscrollEffectSet;
219 public static final int DRAG_BITMAP_PADDING = 2;
220 private boolean mWorkspaceFadeInAdjacentScreens;
221
222 WallpaperOffsetInterpolator mWallpaperOffset;
223 private boolean mWallpaperIsLiveWallpaper;
224 private int mNumPagesForWallpaperParallax;
225 private float mLastSetWallpaperOffsetSteps = 0;
226
227 private Runnable mDelayedResizeRunnable;
228 private Runnable mDelayedSnapToPageRunnable;
229 private Point mDisplaySize = new Point();
230 private int mCameraDistance;
231
232 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
233 private static final int FOLDER_CREATION_TIMEOUT = 0;
234 public static final int REORDER_TIMEOUT = 350;
235 private final Alarm mFolderCreationAlarm = new Alarm();
236 private final Alarm mReorderAlarm = new Alarm();
237 private FolderRingAnimator mDragFolderRingAnimator = null;
238 private FolderIcon mDragOverFolderIcon = null;
239 private boolean mCreateUserFolderOnDrop = false;
240 private boolean mAddToExistingFolderOnDrop = false;
241 private DropTarget.DragEnforcer mDragEnforcer;
242 private float mMaxDistanceForFolderCreation;
243
244 private final Canvas mCanvas = new Canvas();
245
246 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
247 private float mXDown;
248 private float mYDown;
249 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
250 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
251 final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
252
253 // Relating to the animation of items being dropped externally
254 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
255 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
256 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
257 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
258 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
259
260 // Related to dragging, folder creation and reordering
261 private static final int DRAG_MODE_NONE = 0;
262 private static final int DRAG_MODE_CREATE_FOLDER = 1;
263 private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
264 private static final int DRAG_MODE_REORDER = 3;
265 private int mDragMode = DRAG_MODE_NONE;
266 private int mLastReorderX = -1;
267 private int mLastReorderY = -1;
268
269 private SparseArray<Parcelable> mSavedStates;
270 private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
271
272 // These variables are used for storing the initial and final values during workspace animations
273 private int mSavedScrollX;
274 private float mSavedRotationY;
275 private float mSavedTranslationX;
276
277 private float mCurrentScale;
278 private float mNewScale;
279 private float[] mOldBackgroundAlphas;
280 private float[] mOldAlphas;
281 private float[] mNewBackgroundAlphas;
282 private float[] mNewAlphas;
283 private int mLastChildCount = -1;
284 private float mTransitionProgress;
285
286 float mOverScrollEffect = 0f;
287
288 private Runnable mDeferredAction;
289 private boolean mDeferDropAfterUninstall;
290 private boolean mUninstallSuccessful;
291
292 private final Runnable mBindPages = new Runnable() {
293 @Override
294 public void run() {
295 mLauncher.getModel().bindRemainingSynchronousPages();
296 }
297 };
298
299 /**
300 * Used to inflate the Workspace from XML.
301 *
302 * @param context The application's context.
303 * @param attrs The attributes set containing the Workspace's customization values.
304 */
305 public Workspace(Context context, AttributeSet attrs) {
306 this(context, attrs, 0);
307 }
308
309 /**
310 * Used to inflate the Workspace from XML.
311 *
312 * @param context The application's context.
313 * @param attrs The attributes set containing the Workspace's customization values.
314 * @param defStyle Unused.
315 */
316 public Workspace(Context context, AttributeSet attrs, int defStyle) {
317 super(context, attrs, defStyle);
318 mContentIsRefreshable = false;
319
320 mOutlineHelper = HolographicOutlineHelper.obtain(context);
321
322 mDragEnforcer = new DropTarget.DragEnforcer(context);
323 // With workspace, data is available straight from the get-go
324 setDataIsReady();
325
326 mLauncher = (Launcher) context;
327 final Resources res = getResources();
328 mWorkspaceFadeInAdjacentScreens = LauncherAppState.getInstance().getDynamicGrid().
329 getDeviceProfile().shouldFadeAdjacentWorkspaceScreens();
330 mFadeInAdjacentScreens = false;
331 mWallpaperManager = WallpaperManager.getInstance(context);
332
333 LauncherAppState app = LauncherAppState.getInstance();
334 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
335 TypedArray a = context.obtainStyledAttributes(attrs,
336 R.styleable.Workspace, defStyle, 0);
337 mSpringLoadedShrinkFactor =
338 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
339 mOverviewModeShrinkFactor = grid.getOverviewModeScale();
340 mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
341 mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
342 a.recycle();
343
344 setOnHierarchyChangeListener(this);
345 setHapticFeedbackEnabled(false);
346
347 initWorkspace();
348
349 // Disable multitouch across the workspace/all apps/customize tray
350 setMotionEventSplittingEnabled(true);
351 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
352 }
353
354 @Override
355 public void setInsets(Rect insets) {
356 mInsets.set(insets);
357
358 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
359 if (customScreen != null) {
360 View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
361 if (customContent instanceof Insettable) {
362 ((Insettable) customContent).setInsets(mInsets);
363 }
364 }
365 }
366
367 // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
368 // dimension if unsuccessful
369 public int[] estimateItemSize(int hSpan, int vSpan,
370 ItemInfo itemInfo, boolean springLoaded) {
371 int[] size = new int[2];
372 if (getChildCount() > 0) {
373 // Use the first non-custom page to estimate the child position
374 CellLayout cl = (CellLayout) getChildAt(numCustomPages());
375 Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
376 size[0] = r.width();
377 size[1] = r.height();
378 if (springLoaded) {
379 size[0] *= mSpringLoadedShrinkFactor;
380 size[1] *= mSpringLoadedShrinkFactor;
381 }
382 return size;
383 } else {
384 size[0] = Integer.MAX_VALUE;
385 size[1] = Integer.MAX_VALUE;
386 return size;
387 }
388 }
389
390 public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
391 int hCell, int vCell, int hSpan, int vSpan) {
392 Rect r = new Rect();
393 cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
394 return r;
395 }
396
397 public void onDragStart(final DragSource source, Object info, int dragAction) {
398 mIsDragOccuring = true;
399 updateChildrenLayersEnabled(false);
400 mLauncher.lockScreenOrientation();
401 mLauncher.onInteractionBegin();
402 setChildrenBackgroundAlphaMultipliers(1f);
403 // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
404 InstallShortcutReceiver.enableInstallQueue();
405 UninstallShortcutReceiver.enableUninstallQueue();
406 post(new Runnable() {
407 @Override
408 public void run() {
409 if (mIsDragOccuring) {
410 mDeferRemoveExtraEmptyScreen = false;
411 addExtraEmptyScreenOnDrag();
412 }
413 }
414 });
415 }
416
417
418 public void deferRemoveExtraEmptyScreen() {
419 mDeferRemoveExtraEmptyScreen = true;
420 }
421
422 public void onDragEnd() {
423 if (!mDeferRemoveExtraEmptyScreen) {
424 removeExtraEmptyScreen(true, mDragSourceInternal != null);
425 }
426
427 mIsDragOccuring = false;
428 updateChildrenLayersEnabled(false);
429 mLauncher.unlockScreenOrientation(false);
430
431 // Re-enable any Un/InstallShortcutReceiver and now process any queued items
432 InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
433 UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
434
435 mDragSourceInternal = null;
436 mLauncher.onInteractionEnd();
437 }
438
439 /**
440 * Initializes various states for this workspace.
441 */
442 protected void initWorkspace() {
443 mCurrentPage = mDefaultPage;
444 Launcher.setScreen(mCurrentPage);
445 LauncherAppState app = LauncherAppState.getInstance();
446 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
447 mIconCache = app.getIconCache();
448 setWillNotDraw(false);
449 setClipChildren(false);
450 setClipToPadding(false);
451 setChildrenDrawnWithCacheEnabled(true);
452
453 setMinScale(mOverviewModeShrinkFactor);
454 setupLayoutTransition();
455
456 mWallpaperOffset = new WallpaperOffsetInterpolator();
457 Display display = mLauncher.getWindowManager().getDefaultDisplay();
458 display.getSize(mDisplaySize);
459
460 mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
461 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
462
463 // Set the wallpaper dimensions when Launcher starts up
464 setWallpaperDimension();
465 }
466
467 private void setupLayoutTransition() {
468 // We want to show layout transitions when pages are deleted, to close the gap.
469 mLayoutTransition = new LayoutTransition();
470 mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
471 mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
472 mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
473 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
474 setLayoutTransition(mLayoutTransition);
475 }
476
477 void enableLayoutTransitions() {
478 setLayoutTransition(mLayoutTransition);
479 }
480 void disableLayoutTransitions() {
481 setLayoutTransition(null);
482 }
483
484 @Override
485 protected int getScrollMode() {
486 return SmoothPagedView.X_LARGE_MODE;
487 }
488
489 @Override
490 public void onChildViewAdded(View parent, View child) {
491 if (!(child instanceof CellLayout)) {
492 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
493 }
494 CellLayout cl = ((CellLayout) child);
495 cl.setOnInterceptTouchListener(this);
496 cl.setClickable(true);
497 cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
498 super.onChildViewAdded(parent, child);
499 }
500
501 protected boolean shouldDrawChild(View child) {
502 final CellLayout cl = (CellLayout) child;
503 return super.shouldDrawChild(child) &&
504 (mIsSwitchingState ||
505 cl.getShortcutsAndWidgets().getAlpha() > 0 ||
506 cl.getBackgroundAlpha() > 0);
507 }
508
509 /**
510 * @return The open folder on the current screen, or null if there is none
511 */
512 Folder getOpenFolder() {
513 DragLayer dragLayer = mLauncher.getDragLayer();
514 int count = dragLayer.getChildCount();
515 for (int i = 0; i < count; i++) {
516 View child = dragLayer.getChildAt(i);
517 if (child instanceof Folder) {
518 Folder folder = (Folder) child;
519 if (folder.getInfo().opened)
520 return folder;
521 }
522 }
523 return null;
524 }
525
526 boolean isTouchActive() {
527 return mTouchState != TOUCH_STATE_REST;
528 }
529
530 public void removeAllWorkspaceScreens() {
531 // Disable all layout transitions before removing all pages to ensure that we don't get the
532 // transition animations competing with us changing the scroll when we add pages or the
533 // custom content screen
534 disableLayoutTransitions();
535
536 // Since we increment the current page when we call addCustomContentPage via bindScreens
537 // (and other places), we need to adjust the current page back when we clear the pages
538 if (hasCustomContent()) {
539 removeCustomContentPage();
540 }
541
542 // Remove the pages and clear the screen models
543 removeAllViews();
544 mScreenOrder.clear();
545 mWorkspaceScreens.clear();
546
547 // Re-enable the layout transitions
548 enableLayoutTransitions();
549 }
550
551 public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
552 // Find the index to insert this view into. If the empty screen exists, then
553 // insert it before that.
554 int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
555 if (insertIndex < 0) {
556 insertIndex = mScreenOrder.size();
557 }
558 return insertNewWorkspaceScreen(screenId, insertIndex);
559 }
560
561 public long insertNewWorkspaceScreen(long screenId) {
562 return insertNewWorkspaceScreen(screenId, getChildCount());
563 }
564
565 public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
566 // Log to disk
567 Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
568 " at index: " + insertIndex, true);
569
570 if (mWorkspaceScreens.containsKey(screenId)) {
571 throw new RuntimeException("Screen id " + screenId + " already exists!");
572 }
573
574 CellLayout newScreen = (CellLayout)
575 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
576
577 newScreen.setOnLongClickListener(mLongClickListener);
578 newScreen.setOnClickListener(mLauncher);
579 newScreen.setSoundEffectsEnabled(false);
580 mWorkspaceScreens.put(screenId, newScreen);
581 mScreenOrder.add(insertIndex, screenId);
582 addView(newScreen, insertIndex);
583 return screenId;
584 }
585
586 public void createCustomContentContainer() {
587 CellLayout customScreen = (CellLayout)
588 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
589 customScreen.disableBackground();
590 customScreen.disableDragTarget();
591
592 mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
593 mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
594
595 // We want no padding on the custom content
596 customScreen.setPadding(0, 0, 0, 0);
597
598 addFullScreenPage(customScreen);
599
600 // Ensure that the current page and default page are maintained.
601 mDefaultPage = mOriginalDefaultPage + 1;
602
603 // Update the custom content hint
604 if (mRestorePage != INVALID_RESTORE_PAGE) {
605 mRestorePage = mRestorePage + 1;
606 } else {
607 setCurrentPage(getCurrentPage() + 1);
608 }
609 }
610
611 public void removeCustomContentPage() {
612 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
613 if (customScreen == null) {
614 throw new RuntimeException("Expected custom content screen to exist");
615 }
616
617 mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
618 mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
619 removeView(customScreen);
620
621 if (mCustomContentCallbacks != null) {
622 mCustomContentCallbacks.onScrollProgressChanged(0);
623 mCustomContentCallbacks.onHide();
624 }
625
626 mCustomContentCallbacks = null;
627
628 // Ensure that the current page and default page are maintained.
629 mDefaultPage = mOriginalDefaultPage - 1;
630
631 // Update the custom content hint
632 if (mRestorePage != INVALID_RESTORE_PAGE) {
633 mRestorePage = mRestorePage - 1;
634 } else {
635 setCurrentPage(getCurrentPage() - 1);
636 }
637 }
638
639 public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
640 String description) {
641 if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
642 throw new RuntimeException("Expected custom content screen to exist");
643 }
644
645 // Add the custom content to the full screen custom page
646 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
647 int spanX = customScreen.getCountX();
648 int spanY = customScreen.getCountY();
649 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
650 lp.canReorder = false;
651 lp.isFullscreen = true;
652 if (customContent instanceof Insettable) {
653 ((Insettable)customContent).setInsets(mInsets);
654 }
655
656 // Verify that the child is removed from any existing parent.
657 if (customContent.getParent() instanceof ViewGroup) {
658 ViewGroup parent = (ViewGroup) customContent.getParent();
659 parent.removeView(customContent);
660 }
661 customScreen.removeAllViews();
662 customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
663 mCustomContentDescription = description;
664
665 mCustomContentCallbacks = callbacks;
666 }
667
668 public void addExtraEmptyScreenOnDrag() {
669 // Log to disk
670 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
671
672 boolean lastChildOnScreen = false;
673 boolean childOnFinalScreen = false;
674
675 // Cancel any pending removal of empty screen
676 mRemoveEmptyScreenRunnable = null;
677
678 if (mDragSourceInternal != null) {
679 if (mDragSourceInternal.getChildCount() == 1) {
680 lastChildOnScreen = true;
681 }
682 CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
683 if (indexOfChild(cl) == getChildCount() - 1) {
684 childOnFinalScreen = true;
685 }
686 }
687
688 // If this is the last item on the final screen
689 if (lastChildOnScreen && childOnFinalScreen) {
690 return;
691 }
692 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
693 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
694 }
695 }
696
697 public boolean addExtraEmptyScreen() {
698 // Log to disk
699 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
700
701 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
702 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
703 return true;
704 }
705 return false;
706 }
707
708 private void convertFinalScreenToEmptyScreenIfNecessary() {
709 // Log to disk
710 Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
711
712 if (mLauncher.isWorkspaceLoading()) {
713 // Invalid and dangerous operation if workspace is loading
714 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
715 return;
716 }
717
718 if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
719 long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
720
721 if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
722 CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
723
724 // If the final screen is empty, convert it to the extra empty screen
725 if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
726 !finalScreen.isDropPending()) {
727 mWorkspaceScreens.remove(finalScreenId);
728 mScreenOrder.remove(finalScreenId);
729
730 // if this is the last non-custom content screen, convert it to the empty screen
731 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
732 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
733
734 // Update the model if we have changed any screens
735 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
736 Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
737 }
738 }
739
740 public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
741 removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
742 }
743
744 public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
745 final int delay, final boolean stripEmptyScreens) {
746 // Log to disk
747 Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
748 if (mLauncher.isWorkspaceLoading()) {
749 // Don't strip empty screens if the workspace is still loading
750 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
751 return;
752 }
753
754 if (delay > 0) {
755 postDelayed(new Runnable() {
756 @Override
757 public void run() {
758 removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
759 }
760 }, delay);
761 return;
762 }
763
764 convertFinalScreenToEmptyScreenIfNecessary();
765 if (hasExtraEmptyScreen()) {
766 int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
767 if (getNextPage() == emptyIndex) {
768 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
769 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
770 onComplete, stripEmptyScreens);
771 } else {
772 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
773 onComplete, stripEmptyScreens);
774 }
775 return;
776 } else if (stripEmptyScreens) {
777 // If we're not going to strip the empty screens after removing
778 // the extra empty screen, do it right away.
779 stripEmptyScreens();
780 }
781
782 if (onComplete != null) {
783 onComplete.run();
784 }
785 }
786
787 private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
788 final boolean stripEmptyScreens) {
789 // Log to disk
790 // XXX: Do we need to update LM workspace screens below?
791 Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
792 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
793 PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
794
795 final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
796
797 mRemoveEmptyScreenRunnable = new Runnable() {
798 @Override
799 public void run() {
800 if (hasExtraEmptyScreen()) {
801 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
802 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
803 removeView(cl);
804 if (stripEmptyScreens) {
805 stripEmptyScreens();
806 }
807 }
808 }
809 };
810
811 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
812 oa.setDuration(duration);
813 oa.setStartDelay(delay);
814 oa.addListener(new AnimatorListenerAdapter() {
815 @Override
816 public void onAnimationEnd(Animator animation) {
817 if (mRemoveEmptyScreenRunnable != null) {
818 mRemoveEmptyScreenRunnable.run();
819 }
820 if (onComplete != null) {
821 onComplete.run();
822 }
823 }
824 });
825 oa.start();
826 }
827
828 public boolean hasExtraEmptyScreen() {
829 int nScreens = getChildCount();
830 nScreens = nScreens - numCustomPages();
831 return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
832 }
833
834 public long commitExtraEmptyScreen() {
835 // Log to disk
836 Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
837 if (mLauncher.isWorkspaceLoading()) {
838 // Invalid and dangerous operation if workspace is loading
839 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
840 return -1;
841 }
842
843 int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
844 CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
845 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
846 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
847
848 long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
849 mWorkspaceScreens.put(newId, cl);
850 mScreenOrder.add(newId);
851
852 // Update the page indicator marker
853 if (getPageIndicator() != null) {
854 getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
855 }
856
857 // Update the model for the new screen
858 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
859
860 return newId;
861 }
862
863 public CellLayout getScreenWithId(long screenId) {
864 CellLayout layout = mWorkspaceScreens.get(screenId);
865 return layout;
866 }
867
868 public long getIdForScreen(CellLayout layout) {
869 Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
870 while (iter.hasNext()) {
871 long id = iter.next();
872 if (mWorkspaceScreens.get(id) == layout) {
873 return id;
874 }
875 }
876 return -1;
877 }
878
879 public int getPageIndexForScreenId(long screenId) {
880 return indexOfChild(mWorkspaceScreens.get(screenId));
881 }
882
883 public long getScreenIdForPageIndex(int index) {
884 if (0 <= index && index < mScreenOrder.size()) {
885 return mScreenOrder.get(index);
886 }
887 return -1;
888 }
889
890 ArrayList<Long> getScreenOrder() {
891 return mScreenOrder;
892 }
893
894 public void stripEmptyScreens() {
895 // Log to disk
896 Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
897
898 <<<<<<< GitAnalyzerPlus_ours
899 if (mLauncher.isWorkspaceLoading()) {
900 // Don't strip empty screens if the workspace is still loading.
901 // This is dangerous and can result in data loss.
902 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
903 return;
904 }
905
906 ||||||| GitAnalyzerPlus_base
907 if (mLauncher.isWorkspaceLoading()) {
908 // Don't strip empty screens if the workspace is still loading
909 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
910 return;
911 }
912
913 if (isPageMoving()) {
914 mStripScreensOnPageStopMoving = true;
915 return;
916 }
917
918 int currentPage = getNextPage();
919 ArrayList<Long> removeScreens = new ArrayList<Long>();
920 for (Long id: mWorkspaceScreens.keySet()) {
921 CellLayout cl = mWorkspaceScreens.get(id);
922 if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
923 removeScreens.add(id);
924 }
925 }
926
927 // We enforce at least one page to add new items to. In the case that we remove the last
928 // such screen, we convert the last screen to the empty screen
929 int minScreens = 1 + numCustomPages();
930
931 int pageShift = 0;
932 for (Long id: removeScreens) {
933 Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
934 CellLayout cl = mWorkspaceScreens.get(id);
935 mWorkspaceScreens.remove(id);
936 mScreenOrder.remove(id);
937
938 if (getChildCount() > minScreens) {
939 if (indexOfChild(cl) < currentPage) {
940 pageShift++;
941 }
942 removeView(cl);
943 } else {
944 // if this is the last non-custom content screen, convert it to the empty screen
945 mRemoveEmptyScreenRunnable = null;
946 =======
947 >>>>>>> GitAnalyzerPlus_theirs
948 if (isPageMoving()) {
949 mStripScreensOnPageStopMoving = true;
950 return;
951 }
952
953 int currentPage = getNextPage();
954 ArrayList<Long> removeScreens = new ArrayList<Long>();
955 for (Long id: mWorkspaceScreens.keySet()) {
956 CellLayout cl = mWorkspaceScreens.get(id);
957 if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
958 removeScreens.add(id);
959 }
960 }
961
962 // We enforce at least one page to add new items to. In the case that we remove the last
963 // such screen, we convert the last screen to the empty screen
964 int minScreens = 1 + numCustomPages();
965
966 int pageShift = 0;
967 for (Long id: removeScreens) {
968 Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
969 CellLayout cl = mWorkspaceScreens.get(id);
970 mWorkspaceScreens.remove(id);
971 mScreenOrder.remove(id);
972
973 if (getChildCount() > minScreens) {
974 if (indexOfChild(cl) < currentPage) {
975 pageShift++;
976 }
977 removeView(cl);
978 } else {
979 // if this is the last non-custom content screen, convert it to the empty screen
980 mRemoveEmptyScreenRunnable = null;
981 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
982 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
983 }
984 }
985
986 if (!removeScreens.isEmpty()) {
987 // Update the model if we have changed any screens
988 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
989 }
990
991 if (pageShift >= 0) {
992 setCurrentPage(currentPage - pageShift);
993 }
994 }
995
996 // See implementation for parameter definition.
997 void addInScreen(View child, long container, long screenId,
998 int x, int y, int spanX, int spanY) {
999 addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
1000 }
1001
1002 // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
1003 // See implementation for parameter definition.
1004 void addInScreenFromBind(View child, long container, long screenId, int x, int y,
1005 int spanX, int spanY) {
1006 addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
1007 }
1008
1009 // See implementation for parameter definition.
1010 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
1011 boolean insert) {
1012 addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
1013 }
1014
1015 /**
1016 * Adds the specified child in the specified screen. The position and dimension of
1017 * the child are defined by x, y, spanX and spanY.
1018 *
1019 * @param child The child to add in one of the workspace's screens.
1020 * @param screenId The screen in which to add the child.
1021 * @param x The X position of the child in the screen's grid.
1022 * @param y The Y position of the child in the screen's grid.
1023 * @param spanX The number of cells spanned horizontally by the child.
1024 * @param spanY The number of cells spanned vertically by the child.
1025 * @param insert When true, the child is inserted at the beginning of the children list.
1026 * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
1027 * the x and y position in which to place hotseat items. Otherwise
1028 * we use the x and y position to compute the rank.
1029 */
1030 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
1031 boolean insert, boolean computeXYFromRank) {
1032 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1033 if (getScreenWithId(screenId) == null) {
1034 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
1035 // DEBUGGING - Print out the stack trace to see where we are adding from
1036 new Throwable().printStackTrace();
1037 return;
1038 }
1039 }
1040 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
1041 // This should never happen
1042 throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
1043 }
1044
1045 final CellLayout layout;
1046 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1047 layout = mLauncher.getHotseat().getLayout();
1048 child.setOnKeyListener(new HotseatIconKeyEventListener());
1049
1050 // Hide folder title in the hotseat
1051 if (child instanceof FolderIcon) {
1052 ((FolderIcon) child).setTextVisible(false);
1053 }
1054
1055 if (computeXYFromRank) {
1056 x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
1057 y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
1058 } else {
1059 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
1060 }
1061 } else {
1062 // Show folder title if not in the hotseat
1063 if (child instanceof FolderIcon) {
1064 ((FolderIcon) child).setTextVisible(true);
1065 }
1066 layout = getScreenWithId(screenId);
1067 child.setOnKeyListener(new IconKeyEventListener());
1068 }
1069
1070 ViewGroup.LayoutParams genericLp = child.getLayoutParams();
1071 CellLayout.LayoutParams lp;
1072 if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
1073 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
1074 } else {
1075 lp = (CellLayout.LayoutParams) genericLp;
1076 lp.cellX = x;
1077 lp.cellY = y;
1078 lp.cellHSpan = spanX;
1079 lp.cellVSpan = spanY;
1080 }
1081
1082 if (spanX < 0 && spanY < 0) {
1083 lp.isLockedToGrid = false;
1084 }
1085
1086 // Get the canonical child id to uniquely represent this view in this screen
1087 ItemInfo info = (ItemInfo) child.getTag();
1088 int childId = mLauncher.getViewIdForItem(info);
1089
1090 boolean markCellsAsOccupied = !(child instanceof Folder);
1091 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
1092 // TODO: This branch occurs when the workspace is adding views
1093 // outside of the defined grid
1094 // maybe we should be deleting these items from the LauncherModel?
1095 Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to Cel🔵
1096 }
1097
1098 if (!(child instanceof Folder)) {
1099 child.setHapticFeedbackEnabled(false);
1100 child.setOnLongClickListener(mLongClickListener);
1101 }
1102 if (child instanceof DropTarget) {
1103 mDragController.addDropTarget((DropTarget) child);
1104 }
1105 }
1106
1107 /**
1108 * Called directly from a CellLayout (not by the framework), after we've been added as a
1109 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
1110 * that it should intercept touch events, which is not something that is normally supported.
1111 */
1112 @Override
1113 public boolean onTouch(View v, MotionEvent event) {
1114 return (workspaceInModalState() || !isFinishedSwitchingState())
1115 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
1116 }
1117
1118 public boolean isSwitchingState() {
1119 return mIsSwitchingState;
1120 }
1121
1122 /** This differs from isSwitchingState in that we take into account how far the transition
1123 * has completed. */
1124 public boolean isFinishedSwitchingState() {
1125 return !mIsSwitchingState || (mTransitionProgress > 0.5f);
1126 }
1127
1128 protected void onWindowVisibilityChanged (int visibility) {
1129 mLauncher.onWindowVisibilityChanged(visibility);
1130 }
1131
1132 @Override
1133 public boolean dispatchUnhandledMove(View focused, int direction) {
1134 if (workspaceInModalState() || !isFinishedSwitchingState()) {
1135 // when the home screens are shrunken, shouldn't allow side-scrolling
1136 return false;
1137 }
1138 return super.dispatchUnhandledMove(focused, direction);
1139 }
1140
1141 @Override
1142 public boolean onInterceptTouchEvent(MotionEvent ev) {
1143 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1144 case MotionEvent.ACTION_DOWN:
1145 mXDown = ev.getX();
1146 mYDown = ev.getY();
1147 mTouchDownTime = System.currentTimeMillis();
1148 break;
1149 case MotionEvent.ACTION_POINTER_UP:
1150 case MotionEvent.ACTION_UP:
1151 if (mTouchState == TOUCH_STATE_REST) {
1152 final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
1153 if (currentPage != null && !currentPage.lastDownOnOccupiedCell()) {
1154 onWallpaperTap(ev);
1155 }
1156 }
1157 }
1158 return super.onInterceptTouchEvent(ev);
1159 }
1160
1161 @Override
1162 public boolean onGenericMotionEvent(MotionEvent event) {
1163 // Ignore pointer scroll events if the custom content doesn't allow scrolling.
1164 if ((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID)
1165 && (mCustomContentCallbacks != null)
1166 && !mCustomContentCallbacks.isScrollingAllowed()) {
1167 return false;
1168 }
1169 return super.onGenericMotionEvent(event);
1170 }
1171
1172 protected void reinflateWidgetsIfNecessary() {
1173 final int clCount = getChildCount();
1174 for (int i = 0; i < clCount; i++) {
1175 CellLayout cl = (CellLayout) getChildAt(i);
1176 ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
1177 final int itemCount = swc.getChildCount();
1178 for (int j = 0; j < itemCount; j++) {
1179 View v = swc.getChildAt(j);
1180
1181 if (v != null && v.getTag() instanceof LauncherAppWidgetInfo) {
1182 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
1183 LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
1184 if (lahv != null && lahv.isReinflateRequired()) {
1185 mLauncher.removeAppWidget(info);
1186 // Remove the current widget which is inflated with the wrong orientation
1187 cl.removeView(lahv);
1188 mLauncher.bindAppWidget(info);
1189 }
1190 }
1191 }
1192 }
1193 }
1194
1195 @Override
1196 protected void determineScrollingStart(MotionEvent ev) {
1197 if (!isFinishedSwitchingState()) return;
1198
1199 float deltaX = ev.getX() - mXDown;
1200 float absDeltaX = Math.abs(deltaX);
1201 float absDeltaY = Math.abs(ev.getY() - mYDown);
1202
1203 if (Float.compare(absDeltaX, 0f) == 0) return;
1204
1205 float slope = absDeltaY / absDeltaX;
1206 float theta = (float) Math.atan(slope);
1207
1208 if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
1209 cancelCurrentPageLongPress();
1210 }
1211
1212 boolean passRightSwipesToCustomContent =
1213 (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
1214
1215 boolean swipeInIgnoreDirection = isLayoutRtl() ? deltaX < 0 : deltaX > 0;
1216 boolean onCustomContentScreen =
1217 getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID;
1218 if (swipeInIgnoreDirection && onCustomContentScreen && passRightSwipesToCustomContent) {
1219 // Pass swipes to the right to the custom content page.
1220 return;
1221 }
1222
1223 if (onCustomContentScreen && (mCustomContentCallbacks != null)
1224 && !mCustomContentCallbacks.isScrollingAllowed()) {
1225 // Don't allow workspace scrolling if the current custom content screen doesn't allow
1226 // scrolling.
1227 return;
1228 }
1229
1230 if (theta > MAX_SWIPE_ANGLE) {
1231 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1232 return;
1233 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1234 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1235 // increase the touch slop to make it harder to begin scrolling the workspace. This
1236 // results in vertically scrolling widgets to more easily. The higher the angle, the
1237 // more we increase touch slop.
1238 theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1239 float extraRatio = (float)
1240 Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
1241 super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
1242 } else {
1243 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1244 super.determineScrollingStart(ev);
1245 }
1246 }
1247
1248 protected void onPageBeginMoving() {
1249 super.onPageBeginMoving();
1250
1251 if (isHardwareAccelerated()) {
1252 updateChildrenLayersEnabled(false);
1253 } else {
1254 if (mNextPage != INVALID_PAGE) {
1255 // we're snapping to a particular screen
1256 enableChildrenCache(mCurrentPage, mNextPage);
1257 } else {
1258 // this is when user is actively dragging a particular screen, they might
1259 // swipe it either left or right (but we won't advance by more than one screen)
1260 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
1261 }
1262 }
1263 }
1264
1265 protected void onPageEndMoving() {
1266 super.onPageEndMoving();
1267
1268 if (isHardwareAccelerated()) {
1269 updateChildrenLayersEnabled(false);
1270 } else {
1271 clearChildrenCache();
1272 }
1273
1274 if (mDragController.isDragging()) {
1275 if (workspaceInModalState()) {
1276 // If we are in springloaded mode, then force an event to check if the current touch
1277 // is under a new page (to scroll to)
1278 mDragController.forceTouchMove();
1279 }
1280 }
1281
1282 if (mDelayedResizeRunnable != null) {
1283 mDelayedResizeRunnable.run();
1284 mDelayedResizeRunnable = null;
1285 }
1286
1287 if (mDelayedSnapToPageRunnable != null) {
1288 mDelayedSnapToPageRunnable.run();
1289 mDelayedSnapToPageRunnable = null;
1290 }
1291 if (mStripScreensOnPageStopMoving) {
1292 stripEmptyScreens();
1293 mStripScreensOnPageStopMoving = false;
1294 }
1295 }
1296
1297 @Override
1298 protected void notifyPageSwitchListener() {
1299 super.notifyPageSwitchListener();
1300 Launcher.setScreen(getNextPage());
1301
1302 if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
1303 mCustomContentShowing = true;
1304 if (mCustomContentCallbacks != null) {
1305 mCustomContentCallbacks.onShow(false);
1306 mCustomContentShowTime = System.currentTimeMillis();
1307 mLauncher.updateVoiceButtonProxyVisible(false);
1308 }
1309 } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
1310 mCustomContentShowing = false;
1311 if (mCustomContentCallbacks != null) {
1312 mCustomContentCallbacks.onHide();
1313 mLauncher.resetQSBScroll();
1314 mLauncher.updateVoiceButtonProxyVisible(false);
1315 }
1316 }
1317 }
1318
1319 protected CustomContentCallbacks getCustomContentCallbacks() {
1320 return mCustomContentCallbacks;
1321 }
1322
1323 protected void setWallpaperDimension() {
1324 new AsyncTask<Void, Void, Void>() {
1325 public Void doInBackground(Void ... args) {
1326 String spKey = WallpaperCropActivity.getSharedPreferencesKey();
1327 SharedPreferences sp =
1328 mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
1329 LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
1330 sp, mLauncher.getWindowManager(), mWallpaperManager,
1331 mLauncher.overrideWallpaperDimensions());
1332 return null;
1333 }
1334 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
1335 }
1336
1337 protected void snapToPage(int whichPage, Runnable r) {
1338 snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
1339 }
1340
1341 protected void snapToPage(int whichPage, int duration, Runnable r) {
1342 if (mDelayedSnapToPageRunnable != null) {
1343 mDelayedSnapToPageRunnable.run();
1344 }
1345 mDelayedSnapToPageRunnable = r;
1346 snapToPage(whichPage, duration);
1347 }
1348
1349 public void snapToScreenId(long screenId) {
1350 snapToScreenId(screenId, null);
1351 }
1352
1353 protected void snapToScreenId(long screenId, Runnable r) {
1354 snapToPage(getPageIndexForScreenId(screenId), r);
1355 }
1356
1357 class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
1358 float mFinalOffset = 0.0f;
1359 float mCurrentOffset = 0.5f; // to force an initial update
1360 boolean mWaitingForUpdate;
1361 Choreographer mChoreographer;
1362 Interpolator mInterpolator;
1363 boolean mAnimating;
1364 long mAnimationStartTime;
1365 float mAnimationStartOffset;
1366 private final int ANIMATION_DURATION = 250;
1367 // Don't use all the wallpaper for parallax until you have at least this many pages
1368 private final int MIN_PARALLAX_PAGE_SPAN = 3;
1369 int mNumScreens;
1370
1371 public WallpaperOffsetInterpolator() {
1372 mChoreographer = Choreographer.getInstance();
1373 mInterpolator = new DecelerateInterpolator(1.5f);
1374 }
1375
1376 @Override
1377 public void doFrame(long frameTimeNanos) {
1378 updateOffset(false);
1379 }
1380
1381 private void updateOffset(boolean force) {
1382 if (mWaitingForUpdate || force) {
1383 mWaitingForUpdate = false;
1384 if (computeScrollOffset() && mWindowToken != null) {
1385 try {
1386 mWallpaperManager.setWallpaperOffsets(mWindowToken,
1387 mWallpaperOffset.getCurrX(), 0.5f);
1388 setWallpaperOffsetSteps();
1389 } catch (IllegalArgumentException e) {
1390 Log.e(TAG, "Error updating wallpaper offset: " + e);
1391 }
1392 }
1393 }
1394 }
1395
1396 public boolean computeScrollOffset() {
1397 final float oldOffset = mCurrentOffset;
1398 if (mAnimating) {
1399 long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
1400 float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
1401 float t1 = mInterpolator.getInterpolation(t0);
1402 mCurrentOffset = mAnimationStartOffset +
1403 (mFinalOffset - mAnimationStartOffset) * t1;
1404 mAnimating = durationSinceAnimation < ANIMATION_DURATION;
1405 } else {
1406 mCurrentOffset = mFinalOffset;
1407 }
1408
1409 if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
1410 scheduleUpdate();
1411 }
1412 if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
1413 return true;
1414 }
1415 return false;
1416 }
1417
1418 private float wallpaperOffsetForCurrentScroll() {
1419 if (getChildCount() <= 1) {
1420 return 0;
1421 }
1422
1423 // Exclude the leftmost page
1424 int emptyExtraPages = numEmptyScreensToIgnore();
1425 int firstIndex = numCustomPages();
1426 // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
1427 int lastIndex = getChildCount() - 1 - emptyExtraPages;
1428 if (isLayoutRtl()) {
1429 int temp = firstIndex;
1430 firstIndex = lastIndex;
1431 lastIndex = temp;
1432 }
1433
1434 int firstPageScrollX = getScrollForPage(firstIndex);
1435 int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
1436 if (scrollRange == 0) {
1437 return 0;
1438 } else {
1439 // TODO: do different behavior if it's a live wallpaper?
1440 // Sometimes the left parameter of the pages is animated during a layout transition;
1441 // this parameter offsets it to keep the wallpaper from animating as well
1442 int adjustedScroll =
1443 getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
1444 float offset = Math.min(1, adjustedScroll / (float) scrollRange);
1445 offset = Math.max(0, offset);
1446 // Don't use up all the wallpaper parallax until you have at least
1447 // MIN_PARALLAX_PAGE_SPAN pages
1448 int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
1449 int parallaxPageSpan;
1450 if (mWallpaperIsLiveWallpaper) {
1451 parallaxPageSpan = numScrollingPages - 1;
1452 } else {
1453 parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
1454 }
1455 mNumPagesForWallpaperParallax = parallaxPageSpan;
1456
1457 // On RTL devices, push the wallpaper offset to the right if we don't have enough
1458 // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
1459 int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
1460 return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
1461 }
1462 }
1463
1464 private int numEmptyScreensToIgnore() {
1465 int numScrollingPages = getChildCount() - numCustomPages();
1466 if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
1467 return 1;
1468 } else {
1469 return 0;
1470 }
1471 }
1472
1473 private int getNumScreensExcludingEmptyAndCustom() {
1474 int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
1475 return numScrollingPages;
1476 }
1477
1478 public void syncWithScroll() {
1479 float offset = wallpaperOffsetForCurrentScroll();
1480 mWallpaperOffset.setFinalX(offset);
1481 updateOffset(true);
1482 }
1483
1484 public float getCurrX() {
1485 return mCurrentOffset;
1486 }
1487
1488 public float getFinalX() {
1489 return mFinalOffset;
1490 }
1491
1492 private void animateToFinal() {
1493 mAnimating = true;
1494 mAnimationStartOffset = mCurrentOffset;
1495 mAnimationStartTime = System.currentTimeMillis();
1496 }
1497
1498 private void setWallpaperOffsetSteps() {
1499 // Set wallpaper offset steps (1 / (number of screens - 1))
1500 float xOffset = 1.0f / mNumPagesForWallpaperParallax;
1501 if (xOffset != mLastSetWallpaperOffsetSteps) {
1502 mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
1503 mLastSetWallpaperOffsetSteps = xOffset;
1504 }
1505 }
1506
1507 public void setFinalX(float x) {
1508 scheduleUpdate();
1509 mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
1510 if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
1511 if (mNumScreens > 0) {
1512 // Don't animate if we're going from 0 screens
1513 animateToFinal();
1514 }
1515 mNumScreens = getNumScreensExcludingEmptyAndCustom();
1516 }
1517 }
1518
1519 private void scheduleUpdate() {
1520 if (!mWaitingForUpdate) {
1521 mChoreographer.postFrameCallback(this);
1522 mWaitingForUpdate = true;
1523 }
1524 }
1525
1526 public void jumpToFinal() {
1527 mCurrentOffset = mFinalOffset;
1528 }
1529 }
1530
1531 @Override
1532 public void computeScroll() {
1533 super.computeScroll();
1534 mWallpaperOffset.syncWithScroll();
1535 }
1536
1537 @Override
1538 public void announceForAccessibility(CharSequence text) {
1539 // Don't announce if apps is on top of us.
1540 if (!mLauncher.isAllAppsVisible()) {
1541 super.announceForAccessibility(text);
1542 }
1543 }
1544
1545 void showOutlines() {
1546 if (!workspaceInModalState() && !mIsSwitchingState) {
1547 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1548 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1549 mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0🔵
1550 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1551 mChildrenOutlineFadeInAnimation.start();
1552 }
1553 }
1554
1555 void hideOutlines() {
1556 if (!workspaceInModalState() && !mIsSwitchingState) {
1557 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1558 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1559 mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.🔵
1560 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1561 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1562 mChildrenOutlineFadeOutAnimation.start();
1563 }
1564 }
1565
1566 public void showOutlinesTemporarily() {
1567 if (!mIsPageMoving && !isTouchActive()) {
1568 snapToPage(mCurrentPage);
1569 }
1570 }
1571
1572 public void setChildrenOutlineAlpha(float alpha) {
1573 mChildrenOutlineAlpha = alpha;
1574 for (int i = 0; i < getChildCount(); i++) {
1575 CellLayout cl = (CellLayout) getChildAt(i);
1576 cl.setBackgroundAlpha(alpha);
1577 }
1578 }
1579
1580 public float getChildrenOutlineAlpha() {
1581 return mChildrenOutlineAlpha;
1582 }
1583
1584 private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1585 final DragLayer dragLayer = mLauncher.getDragLayer();
1586
1587 if (mBackgroundFadeInAnimation != null) {
1588 mBackgroundFadeInAnimation.cancel();
1589 mBackgroundFadeInAnimation = null;
1590 }
1591 if (mBackgroundFadeOutAnimation != null) {
1592 mBackgroundFadeOutAnimation.cancel();
1593 mBackgroundFadeOutAnimation = null;
1594 }
1595 float startAlpha = dragLayer.getBackgroundAlpha();
1596 if (finalAlpha != startAlpha) {
1597 if (animated) {
1598 mBackgroundFadeOutAnimation =
1599 LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1600 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1601 public void onAnimationUpdate(ValueAnimator animation) {
1602 dragLayer.setBackgroundAlpha(
1603 ((Float)animation.getAnimatedValue()).floatValue());
1604 }
1605 });
1606 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1607 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1608 mBackgroundFadeOutAnimation.start();
1609 } else {
1610 dragLayer.setBackgroundAlpha(finalAlpha);
1611 }
1612 }
1613 }
1614
1615 float backgroundAlphaInterpolator(float r) {
1616 float pivotA = 0.1f;
1617 float pivotB = 0.4f;
1618 if (r < pivotA) {
1619 return 0;
1620 } else if (r > pivotB) {
1621 return 1.0f;
1622 } else {
1623 return (r - pivotA)/(pivotB - pivotA);
1624 }
1625 }
1626
1627 private void updatePageAlphaValues(int screenCenter) {
1628 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1629 if (mWorkspaceFadeInAdjacentScreens &&
1630 !workspaceInModalState() &&
1631 !mIsSwitchingState &&
1632 !isInOverscroll) {
1633 for (int i = numCustomPages(); i < getChildCount(); i++) {
1634 CellLayout child = (CellLayout) getChildAt(i);
1635 if (child != null) {
1636 float scrollProgress = getScrollProgress(screenCenter, child, i);
1637 float alpha = 1 - Math.abs(scrollProgress);
1638 child.getShortcutsAndWidgets().setAlpha(alpha);
1639 //child.setBackgroundAlphaMultiplier(1 - alpha);
1640 }
1641 }
1642 }
1643 }
1644
1645 private void setChildrenBackgroundAlphaMultipliers(float a) {
1646 for (int i = 0; i < getChildCount(); i++) {
1647 CellLayout child = (CellLayout) getChildAt(i);
1648 child.setBackgroundAlphaMultiplier(a);
1649 }
1650 }
1651
1652 public boolean hasCustomContent() {
1653 return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1654 }
1655
1656 public int numCustomPages() {
1657 return hasCustomContent() ? 1 : 0;
1658 }
1659
1660 public boolean isOnOrMovingToCustomContent() {
1661 return hasCustomContent() && getNextPage() == 0;
1662 }
1663
1664 private void updateStateForCustomContent(int screenCenter) {
1665 float translationX = 0;
1666 float progress = 0;
1667 if (hasCustomContent()) {
1668 int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1669
1670 int scrollDelta = getScrollX() - getScrollForPage(index) -
1671 getLayoutTransitionOffsetForPage(index);
1672 float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1673 translationX = scrollRange - scrollDelta;
1674 progress = (scrollRange - scrollDelta) / scrollRange;
1675
1676 if (isLayoutRtl()) {
1677 translationX = Math.min(0, translationX);
1678 } else {
1679 translationX = Math.max(0, translationX);
1680 }
1681 progress = Math.max(0, progress);
1682 }
1683
1684 if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
1685
1686 CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1687 if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) {
1688 cc.setVisibility(VISIBLE);
1689 }
1690
1691 mLastCustomContentScrollProgress = progress;
1692
1693 mLauncher.getDragLayer().setBackgroundAlpha(progress * 0.8f);
1694
1695 if (mLauncher.getHotseat() != null) {
1696 mLauncher.getHotseat().setTranslationX(translationX);
1697 }
1698
1699 if (getPageIndicator() != null) {
1700 getPageIndicator().setTranslationX(translationX);
1701 }
1702
1703 if (mCustomContentCallbacks != null) {
1704 mCustomContentCallbacks.onScrollProgressChanged(progress);
1705 }
1706 }
1707
1708 @Override
1709 protected OnClickListener getPageIndicatorClickListener() {
1710 AccessibilityManager am = (AccessibilityManager)
1711 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1712 if (!am.isTouchExplorationEnabled()) {
1713 return null;
1714 }
1715 OnClickListener listener = new OnClickListener() {
1716 @Override
1717 public void onClick(View arg0) {
1718 enterOverviewMode();
1719 }
1720 };
1721 return listener;
1722 }
1723
1724 @Override
1725 protected void screenScrolled(int screenCenter) {
1726 final boolean isRtl = isLayoutRtl();
1727 super.screenScrolled(screenCenter);
1728
1729 updatePageAlphaValues(screenCenter);
1730 updateStateForCustomContent(screenCenter);
1731 enableHwLayersOnVisiblePages();
1732
1733 boolean shouldOverScroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1734
1735 if (shouldOverScroll) {
1736 int index = 0;
1737 final int lowerIndex = 0;
1738 final int upperIndex = getChildCount() - 1;
1739
1740 final boolean isLeftPage = mOverScrollX < 0;
1741 index = (!isRtl && isLeftPage) || (isRtl && !isLeftPage) ? lowerIndex : upperIndex;
1742
1743 CellLayout cl = (CellLayout) getChildAt(index);
1744 float effect = Math.abs(mOverScrollEffect);
1745 cl.setOverScrollAmount(Math.abs(effect), isLeftPage);
1746
1747 mOverscrollEffectSet = true;
1748 } else {
1749 if (mOverscrollEffectSet && getChildCount() > 0) {
1750 mOverscrollEffectSet = false;
1751 ((CellLayout) getChildAt(0)).setOverScrollAmount(0, false);
1752 ((CellLayout) getChildAt(getChildCount() - 1)).setOverScrollAmount(0, false);
1753 }
1754 }
1755 }
1756
1757 @Override
1758 protected void overScroll(float amount) {
1759 boolean shouldOverScroll = (amount < 0 && (!hasCustomContent() || isLayoutRtl())) ||
1760 (amount > 0 && (!hasCustomContent() || !isLayoutRtl()));
1761 if (shouldOverScroll) {
1762 dampedOverScroll(amount);
1763 mOverScrollEffect = acceleratedOverFactor(amount);
1764 } else {
1765 mOverScrollEffect = 0;
1766 }
1767 }
1768
1769 protected void onAttachedToWindow() {
1770 super.onAttachedToWindow();
1771 mWindowToken = getWindowToken();
1772 computeScroll();
1773 mDragController.setWindowToken(mWindowToken);
1774 }
1775
1776 protected void onDetachedFromWindow() {
1777 super.onDetachedFromWindow();
1778 mWindowToken = null;
1779 }
1780
1781 protected void onResume() {
1782 if (getPageIndicator() != null) {
1783 // In case accessibility state has changed, we need to perform this on every
1784 // attach to window
1785 OnClickListener listener = getPageIndicatorClickListener();
1786 if (listener != null) {
1787 getPageIndicator().setOnClickListener(listener);
1788 }
1789 }
1790 AccessibilityManager am = (AccessibilityManager)
1791 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1792 sAccessibilityEnabled = am.isEnabled();
1793
1794 // Update wallpaper dimensions if they were changed since last onResume
1795 // (we also always set the wallpaper dimensions in the constructor)
1796 if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
1797 setWallpaperDimension();
1798 }
1799 mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
1800 // Force the wallpaper offset steps to be set again, because another app might have changed
1801 // them
1802 mLastSetWallpaperOffsetSteps = 0f;
1803 }
1804
1805 @Override
1806 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1807 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1808 mWallpaperOffset.syncWithScroll();
1809 mWallpaperOffset.jumpToFinal();
1810 }
1811 super.onLayout(changed, left, top, right, bottom);
1812 }
1813
1814 @Override
1815 protected void onDraw(Canvas canvas) {
1816 super.onDraw(canvas);
1817
1818 // Call back to LauncherModel to finish binding after the first draw
1819 post(mBindPages);
1820 }
1821
1822 @Override
1823 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1824 if (!mLauncher.isAllAppsVisible()) {
1825 final Folder openFolder = getOpenFolder();
1826 if (openFolder != null) {
1827 return openFolder.requestFocus(direction, previouslyFocusedRect);
1828 } else {
1829 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1830 }
1831 }
1832 return false;
1833 }
1834
1835 @Override
1836 public int getDescendantFocusability() {
1837 if (workspaceInModalState()) {
1838 return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1839 }
1840 return super.getDescendantFocusability();
1841 }
1842
1843 @Override
1844 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1845 if (!mLauncher.isAllAppsVisible()) {
1846 final Folder openFolder = getOpenFolder();
1847 if (openFolder != null) {
1848 openFolder.addFocusables(views, direction);
1849 } else {
1850 super.addFocusables(views, direction, focusableMode);
1851 }
1852 }
1853 }
1854
1855 public boolean workspaceInModalState() {
1856 return mState != State.NORMAL;
1857 }
1858
1859 void enableChildrenCache(int fromPage, int toPage) {
1860 if (fromPage > toPage) {
1861 final int temp = fromPage;
1862 fromPage = toPage;
1863 toPage = temp;
1864 }
1865
1866 final int screenCount = getChildCount();
1867
1868 fromPage = Math.max(fromPage, 0);
1869 toPage = Math.min(toPage, screenCount - 1);
1870
1871 for (int i = fromPage; i <= toPage; i++) {
1872 final CellLayout layout = (CellLayout) getChildAt(i);
1873 layout.setChildrenDrawnWithCacheEnabled(true);
1874 layout.setChildrenDrawingCacheEnabled(true);
1875 }
1876 }
1877
1878 void clearChildrenCache() {
1879 final int screenCount = getChildCount();
1880 for (int i = 0; i < screenCount; i++) {
1881 final CellLayout layout = (CellLayout) getChildAt(i);
1882 layout.setChildrenDrawnWithCacheEnabled(false);
1883 // In software mode, we don't want the items to continue to be drawn into bitmaps
1884 if (!isHardwareAccelerated()) {
1885 layout.setChildrenDrawingCacheEnabled(false);
1886 }
1887 }
1888 }
1889
1890 private void updateChildrenLayersEnabled(boolean force) {
1891 boolean small = mState == State.OVERVIEW || mIsSwitchingState;
1892 boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1893
1894 if (enableChildrenLayers != mChildrenLayersEnabled) {
1895 mChildrenLayersEnabled = enableChildrenLayers;
1896 if (mChildrenLayersEnabled) {
1897 enableHwLayersOnVisiblePages();
1898 } else {
1899 for (int i = 0; i < getPageCount(); i++) {
1900 final CellLayout cl = (CellLayout) getChildAt(i);
1901 cl.enableHardwareLayer(false);
1902 }
1903 }
1904 }
1905 }
1906
1907 private void enableHwLayersOnVisiblePages() {
1908 if (mChildrenLayersEnabled) {
1909 final int screenCount = getChildCount();
1910 getVisiblePages(mTempVisiblePagesRange);
1911 int leftScreen = mTempVisiblePagesRange[0];
1912 int rightScreen = mTempVisiblePagesRange[1];
1913 if (leftScreen == rightScreen) {
1914 // make sure we're caching at least two pages always
1915 if (rightScreen < screenCount - 1) {
1916 rightScreen++;
1917 } else if (leftScreen > 0) {
1918 leftScreen--;
1919 }
1920 }
1921
1922 final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1923 for (int i = 0; i < screenCount; i++) {
1924 final CellLayout layout = (CellLayout) getPageAt(i);
1925
1926 // enable layers between left and right screen inclusive, except for the
1927 // customScreen, which may animate its content during transitions.
1928 boolean enableLayer = layout != customScreen &&
1929 leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1930 layout.enableHardwareLayer(enableLayer);
1931 }
1932 }
1933 }
1934
1935 public void buildPageHardwareLayers() {
1936 // force layers to be enabled just for the call to buildLayer
1937 updateChildrenLayersEnabled(true);
1938 if (getWindowToken() != null) {
1939 final int childCount = getChildCount();
1940 for (int i = 0; i < childCount; i++) {
1941 CellLayout cl = (CellLayout) getChildAt(i);
1942 cl.buildHardwareLayer();
1943 }
1944 }
1945 updateChildrenLayersEnabled(false);
1946 }
1947
1948 protected void onWallpaperTap(MotionEvent ev) {
1949 final int[] position = mTempCell;
1950 getLocationOnScreen(position);
1951
1952 int pointerIndex = ev.getActionIndex();
1953 position[0] += (int) ev.getX(pointerIndex);
1954 position[1] += (int) ev.getY(pointerIndex);
1955
1956 mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1957 ev.getAction() == MotionEvent.ACTION_UP
1958 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1959 position[0], position[1], 0, null);
1960 }
1961
1962 /*
1963 * This interpolator emulates the rate at which the perceived scale of an object changes
1964 * as its distance from a camera increases. When this interpolator is applied to a scale
1965 * animation on a view, it evokes the sense that the object is shrinking due to moving away
1966 * from the camera.
1967 */
1968 static class ZInterpolator implements TimeInterpolator {
1969 private float focalLength;
1970
1971 public ZInterpolator(float foc) {
1972 focalLength = foc;
1973 }
1974
1975 public float getInterpolation(float input) {
1976 return (1.0f - focalLength / (focalLength + input)) /
1977 (1.0f - focalLength / (focalLength + 1.0f));
1978 }
1979 }
1980
1981 /*
1982 * The exact reverse of ZInterpolator.
1983 */
1984 static class InverseZInterpolator implements TimeInterpolator {
1985 private ZInterpolator zInterpolator;
1986 public InverseZInterpolator(float foc) {
1987 zInterpolator = new ZInterpolator(foc);
1988 }
1989 public float getInterpolation(float input) {
1990 return 1 - zInterpolator.getInterpolation(1 - input);
1991 }
1992 }
1993
1994 /*
1995 * ZInterpolator compounded with an ease-out.
1996 */
1997 static class ZoomOutInterpolator implements TimeInterpolator {
1998 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1999 private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
2000
2001 public float getInterpolation(float input) {
2002 return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
2003 }
2004 }
2005
2006 /*
2007 * InvereZInterpolator compounded with an ease-out.
2008 */
2009 static class ZoomInInterpolator implements TimeInterpolator {
2010 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
2011 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
2012
2013 public float getInterpolation(float input) {
2014 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
2015 }
2016 }
2017
2018 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
2019
2020 /*
2021 *
2022 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
2023 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
2024 *
2025 * These methods mark the appropriate pages as accepting drops (which alters their visual
2026 * appearance).
2027 *
2028 */
2029 private static Rect getDrawableBounds(Drawable d) {
2030 Rect bounds = new Rect();
2031 d.copyBounds(bounds);
2032 if (bounds.width() == 0 || bounds.height() == 0) {
2033 bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
2034 } else {
2035 bounds.offsetTo(0, 0);
2036 }
2037 if (d instanceof PreloadIconDrawable) {
2038 int inset = -((PreloadIconDrawable) d).getOutset();
2039 bounds.inset(inset, inset);
2040 }
2041 return bounds;
2042 }
2043
2044 public void onExternalDragStartedWithItem(View v) {
2045 // Compose a drag bitmap with the view scaled to the icon size
2046 LauncherAppState app = LauncherAppState.getInstance();
2047 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2048 int iconSize = grid.iconSizePx;
2049 int bmpWidth = v.getMeasuredWidth();
2050 int bmpHeight = v.getMeasuredHeight();
2051
2052 // If this is a text view, use its drawable instead
2053 if (v instanceof TextView) {
2054 TextView tv = (TextView) v;
2055 Drawable d = tv.getCompoundDrawables()[1];
2056 Rect bounds = getDrawableBounds(d);
2057 bmpWidth = bounds.width();
2058 bmpHeight = bounds.height();
2059 }
2060
2061 // Compose the bitmap to create the icon from
2062 Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight,
2063 Bitmap.Config.ARGB_8888);
2064 mCanvas.setBitmap(b);
2065 drawDragView(v, mCanvas, 0);
2066 mCanvas.setBitmap(null);
2067
2068 // The outline is used to visualize where the item will land if dropped
2069 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
2070 }
2071
2072 public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
2073 int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
2074
2075 // The outline is used to visualize where the item will land if dropped
2076 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
2077 }
2078
2079 public void exitWidgetResizeMode() {
2080 DragLayer dragLayer = mLauncher.getDragLayer();
2081 dragLayer.clearAllResizeFrames();
2082 }
2083
2084 private void initAnimationArrays() {
2085 final int childCount = getChildCount();
2086 if (mLastChildCount == childCount) return;
2087
2088 mOldBackgroundAlphas = new float[childCount];
2089 mOldAlphas = new float[childCount];
2090 mNewBackgroundAlphas = new float[childCount];
2091 mNewAlphas = new float[childCount];
2092 }
2093
2094 Animator getChangeStateAnimation(final State state, boolean animated,
2095 ArrayList<View> layerViews) {
2096 return getChangeStateAnimation(state, animated, 0, -1, layerViews);
2097 }
2098
2099 @Override
2100 protected void getFreeScrollPageRange(int[] range) {
2101 getOverviewModePages(range);
2102 }
2103
2104 private void getOverviewModePages(int[] range) {
2105 int start = numCustomPages();
2106 int end = getChildCount() - 1;
2107
2108 range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
2109 range[1] = Math.max(0, end);
2110 }
2111
2112 protected void onStartReordering() {
2113 super.onStartReordering();
2114 showOutlines();
2115 // Reordering handles its own animations, disable the automatic ones.
2116 disableLayoutTransitions();
2117 }
2118
2119 protected void onEndReordering() {
2120 super.onEndReordering();
2121
2122 if (mLauncher.isWorkspaceLoading()) {
2123 // Invalid and dangerous operation if workspace is loading
2124 return;
2125 }
2126
2127 hideOutlines();
2128 mScreenOrder.clear();
2129 int count = getChildCount();
2130 for (int i = 0; i < count; i++) {
2131 CellLayout cl = ((CellLayout) getChildAt(i));
2132 mScreenOrder.add(getIdForScreen(cl));
2133 }
2134
2135 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
2136
2137 // Re-enable auto layout transitions for page deletion.
2138 enableLayoutTransitions();
2139 }
2140
2141 public boolean isInOverviewMode() {
2142 return mState == State.OVERVIEW;
2143 }
2144
2145 public boolean enterOverviewMode() {
2146 if (mTouchState != TOUCH_STATE_REST) {
2147 return false;
2148 }
2149 enableOverviewMode(true, -1, true);
2150 return true;
2151 }
2152
2153 public void exitOverviewMode(boolean animated) {
2154 exitOverviewMode(-1, animated);
2155 }
2156
2157 public void exitOverviewMode(int snapPage, boolean animated) {
2158 enableOverviewMode(false, snapPage, animated);
2159 }
2160
2161 private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
2162 State finalState = Workspace.State.OVERVIEW;
2163 if (!enable) {
2164 finalState = Workspace.State.NORMAL;
2165 }
2166
2167 Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
2168 if (workspaceAnim != null) {
2169 onTransitionPrepare();
2170 workspaceAnim.addListener(new AnimatorListenerAdapter() {
2171 @Override
2172 public void onAnimationEnd(Animator arg0) {
2173 onTransitionEnd();
2174 }
2175 });
2176 workspaceAnim.start();
2177 }
2178 }
2179
2180 int getOverviewModeTranslationY() {
2181 LauncherAppState app = LauncherAppState.getInstance();
2182 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2183 Rect overviewBar = grid.getOverviewModeButtonBarRect();
2184
2185 int availableHeight = getViewportHeight();
2186 int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
2187 int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
2188 int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
2189 - scaledHeight) / 2;
2190
2191 return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
2192 }
2193
2194 boolean shouldVoiceButtonProxyBeVisible() {
2195 if (isOnOrMovingToCustomContent()) {
2196 return false;
2197 }
2198 if (mState != State.NORMAL) {
2199 return false;
2200 }
2201 return true;
2202 }
2203
2204 public void updateInteractionForState() {
2205 if (mState != State.NORMAL) {
2206 mLauncher.onInteractionBegin();
2207 } else {
2208 mLauncher.onInteractionEnd();
2209 }
2210 }
2211
2212 private void setState(State state) {
2213 mState = state;
2214 updateInteractionForState();
2215 updateAccessibilityFlags();
2216 }
2217
2218 State getState() {
2219 return mState;
2220 }
2221
2222 private void updateAccessibilityFlags() {
2223 int accessible = mState == State.NORMAL ?
2224 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
2225 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2226 setImportantForAccessibility(accessible);
2227 }
2228
2229 private static final int HIDE_WORKSPACE_DURATION = 100;
2230
2231 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
2232 return getChangeStateAnimation(state, animated, delay, snapPage, null);
2233 }
2234
2235 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage,
2236 ArrayList<View> layerViews) {
2237 if (mState == state) {
2238 return null;
2239 }
2240
2241 // Initialize animation arrays for the first time if necessary
2242 initAnimationArrays();
2243
2244 AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
2245
2246 final State oldState = mState;
2247 final boolean oldStateIsNormal = (oldState == State.NORMAL);
2248 final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
2249 final boolean oldStateIsNormalHidden = (oldState == State.NORMAL_HIDDEN);
2250 final boolean oldStateIsOverviewHidden = (oldState == State.OVERVIEW_HIDDEN);
2251 final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
2252 setState(state);
2253 final boolean stateIsNormal = (state == State.NORMAL);
2254 final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
2255 final boolean stateIsNormalHidden = (state == State.NORMAL_HIDDEN);
2256 final boolean stateIsOverviewHidden = (state == State.OVERVIEW_HIDDEN);
2257 final boolean stateIsOverview = (state == State.OVERVIEW);
2258 float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
2259 float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
2260 float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
2261 float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
2262 float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
2263 getOverviewModeTranslationY() : 0;
2264
2265 boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
2266 boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
2267 boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
2268 boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
2269 boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
2270
2271 mNewScale = 1.0f;
2272
2273 if (oldStateIsOverview) {
2274 disableFreeScroll();
2275 } else if (stateIsOverview) {
2276 enableFreeScroll();
2277 }
2278
2279 if (state != State.NORMAL) {
2280 if (stateIsSpringLoaded) {
2281 mNewScale = mSpringLoadedShrinkFactor;
2282 } else if (stateIsOverview || stateIsOverviewHidden) {
2283 mNewScale = mOverviewModeShrinkFactor;
2284 }
2285 }
2286
2287 final int duration;
2288 if (workspaceToAllApps || overviewToAllApps) {
2289 duration = HIDE_WORKSPACE_DURATION; //getResources().getInteger(R.integer.config_workspaceUns🔵
2290 } else if (workspaceToOverview || overviewToWorkspace) {
2291 duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
2292 } else {
2293 duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
2294 }
2295
2296 if (snapPage == -1) {
2297 snapPage = getPageNearestToCenterOfScreen();
2298 }
2299 snapToPage(snapPage, duration, mZoomInInterpolator);
2300
2301 for (int i = 0; i < getChildCount(); i++) {
2302 final CellLayout cl = (CellLayout) getChildAt(i);
2303 boolean isCurrentPage = (i == snapPage);
2304 float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
2305 float finalAlpha;
2306 if (stateIsNormalHidden || stateIsOverviewHidden) {
2307 finalAlpha = 0f;
2308 } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
2309 finalAlpha = (i == snapPage || i < numCustomPages()) ? 1f : 0f;
2310 } else {
2311 finalAlpha = 1f;
2312 }
2313
2314 // If we are animating to/from the small state, then hide the side pages and fade the
2315 // current page in
2316 if (!mIsSwitchingState) {
2317 if (workspaceToAllApps || allAppsToWorkspace) {
2318 if (allAppsToWorkspace && isCurrentPage) {
2319 initialAlpha = 0f;
2320 } else if (!isCurrentPage) {
2321 initialAlpha = finalAlpha = 0f;
2322 }
2323 cl.setShortcutAndWidgetAlpha(initialAlpha);
2324 }
2325 }
2326
2327 mOldAlphas[i] = initialAlpha;
2328 mNewAlphas[i] = finalAlpha;
2329 if (animated) {
2330 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
2331 mNewBackgroundAlphas[i] = finalBackgroundAlpha;
2332 } else {
2333 cl.setBackgroundAlpha(finalBackgroundAlpha);
2334 cl.setShortcutAndWidgetAlpha(finalAlpha);
2335 }
2336 }
2337
2338 final View searchBar = mLauncher.getQsbBar();
2339 final View overviewPanel = mLauncher.getOverviewPanel();
2340 final View hotseat = mLauncher.getHotseat();
2341 final View pageIndicator = getPageIndicator();
2342 if (animated) {
2343 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
2344 scale.scaleX(mNewScale)
2345 .scaleY(mNewScale)
2346 .translationY(finalWorkspaceTranslationY)
2347 .setDuration(duration)
2348 .setInterpolator(mZoomInInterpolator);
2349 anim.play(scale);
2350 for (int index = 0; index < getChildCount(); index++) {
2351 final int i = index;
2352 final CellLayout cl = (CellLayout) getChildAt(i);
2353 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
2354 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
2355 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
2356 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
2357 } else {
2358 if (layerViews != null) {
2359 layerViews.add(cl);
2360 }
2361 if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
2362 LauncherViewPropertyAnimator alphaAnim =
2363 new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
2364 alphaAnim.alpha(mNewAlphas[i])
2365 .setDuration(duration)
2366 .setInterpolator(mZoomInInterpolator);
2367 anim.play(alphaAnim);
2368 }
2369 if (mOldBackgroundAlphas[i] != 0 ||
2370 mNewBackgroundAlphas[i] != 0) {
2371 ValueAnimator bgAnim =
2372 LauncherAnimUtils.ofFloat(cl, 0f, 1f);
2373 bgAnim.setInterpolator(mZoomInInterpolator);
2374 bgAnim.setDuration(duration);
2375 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2376 public void onAnimationUpdate(float a, float b) {
2377 cl.setBackgroundAlpha(
2378 a * mOldBackgroundAlphas[i] +
2379 b * mNewBackgroundAlphas[i]);
2380 }
2381 });
2382 anim.play(bgAnim);
2383 }
2384 }
2385 }
2386 Animator pageIndicatorAlpha = null;
2387 if (pageIndicator != null) {
2388 pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
2389 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2390 pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
2391 } else {
2392 // create a dummy animation so we don't need to do null checks later
2393 pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
2394 }
2395
2396 Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
2397 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2398 hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2399
2400 Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
2401 .alpha(finalSearchBarAlpha).withLayer();
2402 searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2403
2404 Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel)
2405 .alpha(finalOverviewPanelAlpha).withLayer();
2406 overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2407
2408 // For animation optimations, we may need to provide the Launcher transition
2409 // with a set of views on which to force build layers in certain scenarios.
2410 hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2411 searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2412 overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2413 if (layerViews != null) {
2414 layerViews.add(hotseat);
2415 layerViews.add(searchBar);
2416 layerViews.add(overviewPanel);
2417 }
2418
2419 if (workspaceToOverview) {
2420 pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
2421 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
2422 overviewPanelAlpha.setInterpolator(null);
2423 } else if (overviewToWorkspace) {
2424 pageIndicatorAlpha.setInterpolator(null);
2425 hotseatAlpha.setInterpolator(null);
2426 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
2427 }
2428
2429 overviewPanelAlpha.setDuration(duration);
2430 pageIndicatorAlpha.setDuration(duration);
2431 hotseatAlpha.setDuration(duration);
2432 searchBarAlpha.setDuration(duration);
2433
2434 anim.play(overviewPanelAlpha);
2435 anim.play(hotseatAlpha);
2436 anim.play(searchBarAlpha);
2437 anim.play(pageIndicatorAlpha);
2438 anim.setStartDelay(delay);
2439 } else {
2440 overviewPanel.setAlpha(finalOverviewPanelAlpha);
2441 AlphaUpdateListener.updateVisibility(overviewPanel);
2442 hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2443 AlphaUpdateListener.updateVisibility(hotseat);
2444 if (pageIndicator != null) {
2445 pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
2446 AlphaUpdateListener.updateVisibility(pageIndicator);
2447 }
2448 searchBar.setAlpha(finalSearchBarAlpha);
2449 AlphaUpdateListener.updateVisibility(searchBar);
2450 updateCustomContentVisibility();
2451 setScaleX(mNewScale);
2452 setScaleY(mNewScale);
2453 setTranslationY(finalWorkspaceTranslationY);
2454 }
2455 mLauncher.updateVoiceButtonProxyVisible(false);
2456
2457 if (stateIsNormal) {
2458 animateBackgroundGradient(0f, animated);
2459 } else {
2460 animateBackgroundGradient(getResources().getInteger(
2461 R.integer.config_workspaceScrimAlpha) / 100f, animated);
2462 }
2463 return anim;
2464 }
2465
2466 static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
2467 View view;
2468 public AlphaUpdateListener(View v) {
2469 view = v;
2470 }
2471
2472 @Override
2473 public void onAnimationUpdate(ValueAnimator arg0) {
2474 updateVisibility(view);
2475 }
2476
2477 public static void updateVisibility(View view) {
2478 // We want to avoid the extra layout pass by setting the views to GONE unless
2479 // accessibility is on, in which case not setting them to GONE causes a glitch.
2480 int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
2481 if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
2482 view.setVisibility(invisibleState);
2483 } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
2484 && view.getVisibility() != VISIBLE) {
2485 view.setVisibility(VISIBLE);
2486 }
2487 }
2488
2489 @Override
2490 public void onAnimationCancel(Animator arg0) {
2491 }
2492
2493 @Override
2494 public void onAnimationEnd(Animator arg0) {
2495 updateVisibility(view);
2496 }
2497
2498 @Override
2499 public void onAnimationRepeat(Animator arg0) {
2500 }
2501
2502 @Override
2503 public void onAnimationStart(Animator arg0) {
2504 // We want the views to be visible for animation, so fade-in/out is visible
2505 view.setVisibility(VISIBLE);
2506 }
2507 }
2508
2509 @Override
2510 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2511 onTransitionPrepare();
2512 }
2513
2514 @Override
2515 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2516 }
2517
2518 @Override
2519 public void onLauncherTransitionStep(Launcher l, float t) {
2520 mTransitionProgress = t;
2521 }
2522
2523 @Override
2524 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2525 onTransitionEnd();
2526 }
2527
2528 private void onTransitionPrepare() {
2529 mIsSwitchingState = true;
2530
2531 // Invalidate here to ensure that the pages are rendered during the state change transition.
2532 invalidate();
2533
2534 updateChildrenLayersEnabled(false);
2535 hideCustomContentIfNecessary();
2536 }
2537
2538 void updateCustomContentVisibility() {
2539 int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2540 if (hasCustomContent()) {
2541 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2542 }
2543 }
2544
2545 void showCustomContentIfNecessary() {
2546 boolean show = mState == Workspace.State.NORMAL;
2547 if (show && hasCustomContent()) {
2548 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2549 }
2550 }
2551
2552 void hideCustomContentIfNecessary() {
2553 boolean hide = mState != Workspace.State.NORMAL;
2554 if (hide && hasCustomContent()) {
2555 disableLayoutTransitions();
2556 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2557 enableLayoutTransitions();
2558 }
2559 }
2560
2561 private void onTransitionEnd() {
2562 mIsSwitchingState = false;
2563 updateChildrenLayersEnabled(false);
2564 showCustomContentIfNecessary();
2565 }
2566
2567 @Override
2568 public View getContent() {
2569 return this;
2570 }
2571
2572 /**
2573 * Draw the View v into the given Canvas.
2574 *
2575 * @param v the view to draw
2576 * @param destCanvas the canvas to draw on
2577 * @param padding the horizontal and vertical padding to use when drawing
2578 */
2579 private static void drawDragView(View v, Canvas destCanvas, int padding) {
2580 final Rect clipRect = sTempRect;
2581 v.getDrawingRect(clipRect);
2582
2583 boolean textVisible = false;
2584
2585 destCanvas.save();
2586 if (v instanceof TextView) {
2587 Drawable d = ((TextView) v).getCompoundDrawables()[1];
2588 Rect bounds = getDrawableBounds(d);
2589 clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
2590 destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top);
2591 d.draw(destCanvas);
2592 } else {
2593 if (v instanceof FolderIcon) {
2594 // For FolderIcons the text can bleed into the icon area, and so we need to
2595 // hide the text completely (which can't be achieved by clipping).
2596 if (((FolderIcon) v).getTextVisible()) {
2597 ((FolderIcon) v).setTextVisible(false);
2598 textVisible = true;
2599 }
2600 }
2601 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2602 destCanvas.clipRect(clipRect, Op.REPLACE);
2603 v.draw(destCanvas);
2604
2605 // Restore text visibility of FolderIcon if necessary
2606 if (textVisible) {
2607 ((FolderIcon) v).setTextVisible(true);
2608 }
2609 }
2610 destCanvas.restore();
2611 }
2612
2613 /**
2614 * Returns a new bitmap to show when the given View is being dragged around.
2615 * Responsibility for the bitmap is transferred to the caller.
2616 * @param expectedPadding padding to add to the drag view. If a different padding was used
2617 * its value will be changed
2618 */
2619 public Bitmap createDragBitmap(View v, AtomicInteger expectedPadding) {
2620 Bitmap b;
2621
2622 int padding = expectedPadding.get();
2623 if (v instanceof TextView) {
2624 Drawable d = ((TextView) v).getCompoundDrawables()[1];
2625 Rect bounds = getDrawableBounds(d);
2626 b = Bitmap.createBitmap(bounds.width() + padding,
2627 bounds.height() + padding, Bitmap.Config.ARGB_8888);
2628 expectedPadding.set(padding - bounds.left - bounds.top);
2629 } else {
2630 b = Bitmap.createBitmap(
2631 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2632 }
2633
2634 mCanvas.setBitmap(b);
2635 drawDragView(v, mCanvas, padding);
2636 mCanvas.setBitmap(null);
2637
2638 return b;
2639 }
2640
2641 /**
2642 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2643 * Responsibility for the bitmap is transferred to the caller.
2644 */
2645 private Bitmap createDragOutline(View v, int padding) {
2646 final int outlineColor = getResources().getColor(R.color.outline_color);
2647 final Bitmap b = Bitmap.createBitmap(
2648 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2649
2650 mCanvas.setBitmap(b);
2651 drawDragView(v, mCanvas, padding);
2652 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor);
2653 mCanvas.setBitmap(null);
2654 return b;
2655 }
2656
2657 /**
2658 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2659 * Responsibility for the bitmap is transferred to the caller.
2660 */
2661 private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h,
2662 boolean clipAlpha) {
2663 final int outlineColor = getResources().getColor(R.color.outline_color);
2664 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2665 mCanvas.setBitmap(b);
2666
2667 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2668 float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2669 (h - padding) / (float) orig.getHeight());
2670 int scaledWidth = (int) (scaleFactor * orig.getWidth());
2671 int scaledHeight = (int) (scaleFactor * orig.getHeight());
2672 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2673
2674 // center the image
2675 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2676
2677 mCanvas.drawBitmap(orig, src, dst, null);
2678 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor,
2679 clipAlpha);
2680 mCanvas.setBitmap(null);
2681
2682 return b;
2683 }
2684
2685 void startDrag(CellLayout.CellInfo cellInfo) {
2686 View child = cellInfo.cell;
2687
2688 // Make sure the drag was started by a long press as opposed to a long click.
2689 if (!child.isInTouchMode()) {
2690 return;
2691 }
2692
2693 mDragInfo = cellInfo;
2694 child.setVisibility(INVISIBLE);
2695 CellLayout layout = (CellLayout) child.getParent().getParent();
2696 layout.prepareChildForDrag(child);
2697
2698 beginDragShared(child, this);
2699 }
2700
2701 public void beginDragShared(View child, DragSource source) {
2702 child.clearFocus();
2703 child.setPressed(false);
2704
2705 // The outline is used to visualize where the item will land if dropped
2706 mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
2707
2708 mLauncher.onDragStarted(child);
2709 // The drag bitmap follows the touch point around on the screen
2710 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2711 final Bitmap b = createDragBitmap(child, padding);
2712
2713 final int bmpWidth = b.getWidth();
2714 final int bmpHeight = b.getHeight();
2715
2716 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2717 int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2718 int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2719 - padding.get() / 2);
2720
2721 LauncherAppState app = LauncherAppState.getInstance();
2722 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2723 Point dragVisualizeOffset = null;
2724 Rect dragRect = null;
2725 if (child instanceof BubbleTextView) {
2726 int iconSize = grid.iconSizePx;
2727 int top = child.getPaddingTop();
2728 int left = (bmpWidth - iconSize) / 2;
2729 int right = left + iconSize;
2730 int bottom = top + iconSize;
2731 dragLayerY += top;
2732 // Note: The drag region is used to calculate drag layer offsets, but the
2733 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2734 dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2735 dragRect = new Rect(left, top, right, bottom);
2736 } else if (child instanceof FolderIcon) {
2737 int previewSize = grid.folderIconSizePx;
2738 dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2739 }
2740
2741 // Clear the pressed state if necessary
2742 if (child instanceof BubbleTextView) {
2743 BubbleTextView icon = (BubbleTextView) child;
2744 icon.clearPressedBackground();
2745 }
2746
2747 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2748 String msg = "Drag started with a view that has no tag set. This "
2749 + "will cause a crash (issue 11627249) down the line. "
2750 + "View: " + child + " tag: " + child.getTag();
2751 throw new IllegalStateException(msg);
2752 }
2753
2754 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2755 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2756 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2757
2758 if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2759 mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2760 }
2761
2762 b.recycle();
2763 }
2764
2765 public void beginExternalDragShared(View child, DragSource source) {
2766 LauncherAppState app = LauncherAppState.getInstance();
2767 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2768 int iconSize = grid.iconSizePx;
2769
2770 // Notify launcher of drag start
2771 mLauncher.onDragStarted(child);
2772
2773 // Compose a new drag bitmap that is of the icon size
2774 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2775 final Bitmap tmpB = createDragBitmap(child, padding);
2776 Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
2777 Paint p = new Paint();
2778 p.setFilterBitmap(true);
2779 mCanvas.setBitmap(b);
2780 mCanvas.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()),
2781 new Rect(0, 0, iconSize, iconSize), p);
2782 mCanvas.setBitmap(null);
2783
2784 // Find the child's location on the screen
2785 int bmpWidth = tmpB.getWidth();
2786 float iconScale = (float) bmpWidth / iconSize;
2787 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY) * iconScale;
2788 int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2789 int dragLayerY = Math.round(mTempXY[1]);
2790
2791 // Note: The drag region is used to calculate drag layer offsets, but the
2792 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2793 Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2794 Rect dragRect = new Rect(0, 0, iconSize, iconSize);
2795
2796 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2797 String msg = "Drag started with a view that has no tag set. This "
2798 + "will cause a crash (issue 11627249) down the line. "
2799 + "View: " + child + " tag: " + child.getTag();
2800 throw new IllegalStateException(msg);
2801 }
2802
2803 // Start the drag
2804 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2805 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2806 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2807
2808 // Recycle temporary bitmaps
2809 tmpB.recycle();
2810 }
2811
2812 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2813 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2814 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2815
2816 final int[] cellXY = new int[2];
2817 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2818 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2819
2820 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2821 cellXY[1]);
2822 }
2823
2824 public boolean transitionStateShouldAllowDrop() {
2825 return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
2826 (mState == State.NORMAL || mState == State.SPRING_LOADED));
2827 }
2828
2829 /**
2830 * {@inheritDoc}
2831 */
2832 public boolean acceptDrop(DragObject d) {
2833 // If it's an external drop (e.g. from All Apps), check if it should be accepted
2834 CellLayout dropTargetLayout = mDropToLayout;
2835 if (d.dragSource != this) {
2836 // Don't accept the drop if we're not over a screen at time of drop
2837 if (dropTargetLayout == null) {
2838 return false;
2839 }
2840 if (!transitionStateShouldAllowDrop()) return false;
2841
2842 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2843 d.dragView, mDragViewVisualCenter);
2844
2845 // We want the point to be mapped to the dragTarget.
2846 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2847 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2848 } else {
2849 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2850 }
2851
2852 int spanX = 1;
2853 int spanY = 1;
2854 if (mDragInfo != null) {
2855 final CellLayout.CellInfo dragCellInfo = mDragInfo;
2856 spanX = dragCellInfo.spanX;
2857 spanY = dragCellInfo.spanY;
2858 } else {
2859 final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2860 spanX = dragInfo.spanX;
2861 spanY = dragInfo.spanY;
2862 }
2863
2864 int minSpanX = spanX;
2865 int minSpanY = spanY;
2866 if (d.dragInfo instanceof PendingAddWidgetInfo) {
2867 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2868 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2869 }
2870
2871 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2872 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2873 mTargetCell);
2874 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2875 mDragViewVisualCenter[1], mTargetCell);
2876 if (mCreateUserFolderOnDrop && willCreateUserFolder((ItemInfo) d.dragInfo,
2877 dropTargetLayout, mTargetCell, distance, true)) {
2878 return true;
2879 }
2880
2881 if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder((ItemInfo) d.dragInfo,
2882 dropTargetLayout, mTargetCell, distance)) {
2883 return true;
2884 }
2885
2886 int[] resultSpan = new int[2];
2887 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2888 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2889 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2890 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2891
2892 // Don't accept the drop if there's no room for the item
2893 if (!foundCell) {
2894 // Don't show the message if we are dropping on the AllApps button and the hotseat
2895 // is full
2896 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2897 if (mTargetCell != null && isHotseat) {
2898 Hotseat hotseat = mLauncher.getHotseat();
2899 if (hotseat.isAllAppsButtonRank(
2900 hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2901 return false;
2902 }
2903 }
2904
2905 mLauncher.showOutOfSpaceMessage(isHotseat);
2906 return false;
2907 }
2908 }
2909
2910 long screenId = getIdForScreen(dropTargetLayout);
2911 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2912 commitExtraEmptyScreen();
2913 }
2914
2915 return true;
2916 }
2917
2918 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2919 distance, boolean considerTimeout) {
2920 if (distance > mMaxDistanceForFolderCreation) return false;
2921 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2922
2923 if (dropOverView != null) {
2924 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2925 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2926 return false;
2927 }
2928 }
2929
2930 boolean hasntMoved = false;
2931 if (mDragInfo != null) {
2932 hasntMoved = dropOverView == mDragInfo.cell;
2933 }
2934
2935 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2936 return false;
2937 }
2938
2939 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2940 boolean willBecomeShortcut =
2941 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2942 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2943
2944 return (aboveShortcut && willBecomeShortcut);
2945 }
2946
2947 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2948 float distance) {
2949 if (distance > mMaxDistanceForFolderCreation) return false;
2950 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2951
2952 if (dropOverView != null) {
2953 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2954 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2955 return false;
2956 }
2957 }
2958
2959 if (dropOverView instanceof FolderIcon) {
2960 FolderIcon fi = (FolderIcon) dropOverView;
2961 if (fi.acceptDrop(dragInfo)) {
2962 return true;
2963 }
2964 }
2965 return false;
2966 }
2967
2968 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2969 int[] targetCell, float distance, boolean external, DragView dragView,
2970 Runnable postAnimationRunnable) {
2971 if (distance > mMaxDistanceForFolderCreation) return false;
2972 View v = target.getChildAt(targetCell[0], targetCell[1]);
2973
2974 boolean hasntMoved = false;
2975 if (mDragInfo != null) {
2976 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2977 hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2978 mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2979 }
2980
2981 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2982 mCreateUserFolderOnDrop = false;
2983 final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2984
2985 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2986 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2987
2988 if (aboveShortcut && willBecomeShortcut) {
2989 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2990 ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2991 // if the drag started here, we need to remove it from the workspace
2992 if (!external) {
2993 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2994 }
2995
2996 Rect folderLocation = new Rect();
2997 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2998 target.removeView(v);
2999
3000 FolderIcon fi =
3001 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
3002 destInfo.cellX = -1;
3003 destInfo.cellY = -1;
3004 sourceInfo.cellX = -1;
3005 sourceInfo.cellY = -1;
3006
3007 // If the dragView is null, we can't animate
3008 boolean animate = dragView != null;
3009 if (animate) {
3010 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
3011 postAnimationRunnable);
3012 } else {
3013 fi.addItem(destInfo);
3014 fi.addItem(sourceInfo);
3015 }
3016 return true;
3017 }
3018 return false;
3019 }
3020
3021 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
3022 float distance, DragObject d, boolean external) {
3023 if (distance > mMaxDistanceForFolderCreation) return false;
3024
3025 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
3026 if (!mAddToExistingFolderOnDrop) return false;
3027 mAddToExistingFolderOnDrop = false;
3028
3029 if (dropOverView instanceof FolderIcon) {
3030 FolderIcon fi = (FolderIcon) dropOverView;
3031 if (fi.acceptDrop(d.dragInfo)) {
3032 fi.onDrop(d);
3033
3034 // if the drag started here, we need to remove it from the workspace
3035 if (!external) {
3036 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
3037 }
3038 return true;
3039 }
3040 }
3041 return false;
3042 }
3043
3044 public void onDrop(final DragObject d) {
3045 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
3046 mDragViewVisualCenter);
3047
3048 CellLayout dropTargetLayout = mDropToLayout;
3049
3050 // We want the point to be mapped to the dragTarget.
3051 if (dropTargetLayout != null) {
3052 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
3053 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3054 } else {
3055 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
3056 }
3057 }
3058
3059 int snapScreen = -1;
3060 boolean resizeOnDrop = false;
3061 if (d.dragSource != this) {
3062 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
3063 (int) mDragViewVisualCenter[1] };
3064 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
3065 } else if (mDragInfo != null) {
3066 final View cell = mDragInfo.cell;
3067
3068 Runnable resizeRunnable = null;
3069 if (dropTargetLayout != null && !d.cancelled) {
3070 // Move internally
3071 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
3072 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
3073 long container = hasMovedIntoHotseat ?
3074 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3075 LauncherSettings.Favorites.CONTAINER_DESKTOP;
3076 long screenId = (mTargetCell[0] < 0) ?
3077 mDragInfo.screenId : getIdForScreen(dropTargetLayout);
3078 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
3079 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
3080 // First we find the cell nearest to point at which the item is
3081 // dropped, without any consideration to whether there is an item there.
3082
3083 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
3084 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
3085 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3086 mDragViewVisualCenter[1], mTargetCell);
3087
3088 // If the item being dropped is a shortcut and the nearest drop
3089 // cell also contains a shortcut, then create a folder with the two shortcuts.
3090 if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
3091 dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
3092 return;
3093 }
3094
3095 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
3096 distance, d, false)) {
3097 return;
3098 }
3099
3100 // Aside from the special case where we're dropping a shortcut onto a shortcut,
3101 // we need to find the nearest cell location that is vacant
3102 ItemInfo item = (ItemInfo) d.dragInfo;
3103 int minSpanX = item.spanX;
3104 int minSpanY = item.spanY;
3105 if (item.minSpanX > 0 && item.minSpanY > 0) {
3106 minSpanX = item.minSpanX;
3107 minSpanY = item.minSpanY;
3108 }
3109
3110 int[] resultSpan = new int[2];
3111 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3112 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
3113 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
3114
3115 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
3116
3117 // if the widget resizes on drop
3118 if (foundCell && (cell instanceof AppWidgetHostView) &&
3119 (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
3120 resizeOnDrop = true;
3121 item.spanX = resultSpan[0];
3122 item.spanY = resultSpan[1];
3123 AppWidgetHostView awhv = (AppWidgetHostView) cell;
3124 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
3125 resultSpan[1]);
3126 }
3127
3128 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
3129 snapScreen = getPageIndexForScreenId(screenId);
3130 snapToPage(snapScreen);
3131 }
3132
3133 if (foundCell) {
3134 final ItemInfo info = (ItemInfo) cell.getTag();
3135 if (hasMovedLayouts) {
3136 // Reparent the view
3137 CellLayout parentCell = getParentCellLayoutForView(cell);
3138 if (parentCell != null) {
3139 parentCell.removeView(cell);
3140 } else if (LauncherAppState.isDogfoodBuild()) {
3141 throw new NullPointerException("mDragInfo.cell has null parent");
3142 }
3143 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
3144 info.spanX, info.spanY);
3145 }
3146
3147 // update the item's position after drop
3148 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
3149 lp.cellX = lp.tmpCellX = mTargetCell[0];
3150 lp.cellY = lp.tmpCellY = mTargetCell[1];
3151 lp.cellHSpan = item.spanX;
3152 lp.cellVSpan = item.spanY;
3153 lp.isLockedToGrid = true;
3154
3155 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3156 cell instanceof LauncherAppWidgetHostView) {
3157 final CellLayout cellLayout = dropTargetLayout;
3158 // We post this call so that the widget has a chance to be placed
3159 // in its final location
3160
3161 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
3162 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
3163 if (pinfo != null &&
3164 pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
3165 final Runnable addResizeFrame = new Runnable() {
3166 public void run() {
3167 DragLayer dragLayer = mLauncher.getDragLayer();
3168 dragLayer.addResizeFrame(info, hostView, cellLayout);
3169 }
3170 };
3171 resizeRunnable = (new Runnable() {
3172 public void run() {
3173 if (!isPageMoving()) {
3174 addResizeFrame.run();
3175 } else {
3176 mDelayedResizeRunnable = addResizeFrame;
3177 }
3178 }
3179 });
3180 }
3181 }
3182
3183 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
3184 lp.cellY, item.spanX, item.spanY);
3185 } else {
3186 // If we can't find a drop location, we return the item to its original position
3187 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
3188 mTargetCell[0] = lp.cellX;
3189 mTargetCell[1] = lp.cellY;
3190 CellLayout layout = (CellLayout) cell.getParent().getParent();
3191 layout.markCellsAsOccupiedForView(cell);
3192 }
3193 }
3194
3195 final CellLayout parent = (CellLayout) cell.getParent().getParent();
3196 final Runnable finalResizeRunnable = resizeRunnable;
3197 // Prepare it to be animated into its new position
3198 // This must be called after the view has been re-parented
3199 final Runnable onCompleteRunnable = new Runnable() {
3200 @Override
3201 public void run() {
3202 mAnimatingViewIntoPlace = false;
3203 updateChildrenLayersEnabled(false);
3204 if (finalResizeRunnable != null) {
3205 finalResizeRunnable.run();
3206 }
3207 }
3208 };
3209 mAnimatingViewIntoPlace = true;
3210 if (d.dragView.hasDrawn()) {
3211 final ItemInfo info = (ItemInfo) cell.getTag();
3212 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
3213 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
3214 ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3215 animateWidgetDrop(info, parent, d.dragView,
3216 onCompleteRunnable, animationType, cell, false);
3217 } else {
3218 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
3219 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
3220 onCompleteRunnable, this);
3221 }
3222 } else {
3223 d.deferDragViewCleanupPostAnimation = false;
3224 cell.setVisibility(VISIBLE);
3225 }
3226 parent.onDropChild(cell);
3227 }
3228 }
3229
3230 public void setFinalScrollForPageChange(int pageIndex) {
3231 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3232 if (cl != null) {
3233 mSavedScrollX = getScrollX();
3234 mSavedTranslationX = cl.getTranslationX();
3235 mSavedRotationY = cl.getRotationY();
3236 final int newX = getScrollForPage(pageIndex);
3237 setScrollX(newX);
3238 cl.setTranslationX(0f);
3239 cl.setRotationY(0f);
3240 }
3241 }
3242
3243 public void resetFinalScrollForPageChange(int pageIndex) {
3244 if (pageIndex >= 0) {
3245 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3246 setScrollX(mSavedScrollX);
3247 cl.setTranslationX(mSavedTranslationX);
3248 cl.setRotationY(mSavedRotationY);
3249 }
3250 }
3251
3252 public void getViewLocationRelativeToSelf(View v, int[] location) {
3253 getLocationInWindow(location);
3254 int x = location[0];
3255 int y = location[1];
3256
3257 v.getLocationInWindow(location);
3258 int vX = location[0];
3259 int vY = location[1];
3260
3261 location[0] = vX - x;
3262 location[1] = vY - y;
3263 }
3264
3265 public void onDragEnter(DragObject d) {
3266 mDragEnforcer.onDragEnter();
3267 mCreateUserFolderOnDrop = false;
3268 mAddToExistingFolderOnDrop = false;
3269
3270 mDropToLayout = null;
3271 CellLayout layout = getCurrentDropLayout();
3272 setCurrentDropLayout(layout);
3273 setCurrentDragOverlappingLayout(layout);
3274
3275 if (!workspaceInModalState()) {
3276 mLauncher.getDragLayer().showPageHints();
3277 }
3278 }
3279
3280 /** Return a rect that has the cellWidth/cellHeight (left, top), and
3281 * widthGap/heightGap (right, bottom) */
3282 static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
3283 LauncherAppState app = LauncherAppState.getInstance();
3284 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3285
3286 Display display = launcher.getWindowManager().getDefaultDisplay();
3287 Point smallestSize = new Point();
3288 Point largestSize = new Point();
3289 display.getCurrentSizeRange(smallestSize, largestSize);
3290 int countX = (int) grid.numColumns;
3291 int countY = (int) grid.numRows;
3292 if (orientation == CellLayout.LANDSCAPE) {
3293 if (mLandscapeCellLayoutMetrics == null) {
3294 Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
3295 int width = largestSize.x - padding.left - padding.right;
3296 int height = smallestSize.y - padding.top - padding.bottom;
3297 mLandscapeCellLayoutMetrics = new Rect();
3298 mLandscapeCellLayoutMetrics.set(
3299 grid.calculateCellWidth(width, countX),
3300 grid.calculateCellHeight(height, countY), 0, 0);
3301 }
3302 return mLandscapeCellLayoutMetrics;
3303 } else if (orientation == CellLayout.PORTRAIT) {
3304 if (mPortraitCellLayoutMetrics == null) {
3305 Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
3306 int width = smallestSize.x - padding.left - padding.right;
3307 int height = largestSize.y - padding.top - padding.bottom;
3308 mPortraitCellLayoutMetrics = new Rect();
3309 mPortraitCellLayoutMetrics.set(
3310 grid.calculateCellWidth(width, countX),
3311 grid.calculateCellHeight(height, countY), 0, 0);
3312 }
3313 return mPortraitCellLayoutMetrics;
3314 }
3315 return null;
3316 }
3317
3318 public void onDragExit(DragObject d) {
3319 mDragEnforcer.onDragExit();
3320
3321 // Here we store the final page that will be dropped to, if the workspace in fact
3322 // receives the drop
3323 if (mInScrollArea) {
3324 if (isPageMoving()) {
3325 // If the user drops while the page is scrolling, we should use that page as the
3326 // destination instead of the page that is being hovered over.
3327 mDropToLayout = (CellLayout) getPageAt(getNextPage());
3328 } else {
3329 mDropToLayout = mDragOverlappingLayout;
3330 }
3331 } else {
3332 mDropToLayout = mDragTargetLayout;
3333 }
3334
3335 if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
3336 mCreateUserFolderOnDrop = true;
3337 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
3338 mAddToExistingFolderOnDrop = true;
3339 }
3340
3341 // Reset the scroll area and previous drag target
3342 onResetScrollArea();
3343 setCurrentDropLayout(null);
3344 setCurrentDragOverlappingLayout(null);
3345
3346 mSpringLoadedDragController.cancel();
3347
3348 if (!mIsPageMoving) {
3349 hideOutlines();
3350 }
3351 mLauncher.getDragLayer().hidePageHints();
3352 }
3353
3354 void setCurrentDropLayout(CellLayout layout) {
3355 if (mDragTargetLayout != null) {
3356 mDragTargetLayout.revertTempState();
3357 mDragTargetLayout.onDragExit();
3358 }
3359 mDragTargetLayout = layout;
3360 if (mDragTargetLayout != null) {
3361 mDragTargetLayout.onDragEnter();
3362 }
3363 cleanupReorder(true);
3364 cleanupFolderCreation();
3365 setCurrentDropOverCell(-1, -1);
3366 }
3367
3368 void setCurrentDragOverlappingLayout(CellLayout layout) {
3369 if (mDragOverlappingLayout != null) {
3370 mDragOverlappingLayout.setIsDragOverlapping(false);
3371 }
3372 mDragOverlappingLayout = layout;
3373 if (mDragOverlappingLayout != null) {
3374 mDragOverlappingLayout.setIsDragOverlapping(true);
3375 }
3376 invalidate();
3377 }
3378
3379 void setCurrentDropOverCell(int x, int y) {
3380 if (x != mDragOverX || y != mDragOverY) {
3381 mDragOverX = x;
3382 mDragOverY = y;
3383 setDragMode(DRAG_MODE_NONE);
3384 }
3385 }
3386
3387 void setDragMode(int dragMode) {
3388 if (dragMode != mDragMode) {
3389 if (dragMode == DRAG_MODE_NONE) {
3390 cleanupAddToFolder();
3391 // We don't want to cancel the re-order alarm every time the target cell changes
3392 // as this feels to slow / unresponsive.
3393 cleanupReorder(false);
3394 cleanupFolderCreation();
3395 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3396 cleanupReorder(true);
3397 cleanupFolderCreation();
3398 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3399 cleanupAddToFolder();
3400 cleanupReorder(true);
3401 } else if (dragMode == DRAG_MODE_REORDER) {
3402 cleanupAddToFolder();
3403 cleanupFolderCreation();
3404 }
3405 mDragMode = dragMode;
3406 }
3407 }
3408
3409 private void cleanupFolderCreation() {
3410 if (mDragFolderRingAnimator != null) {
3411 mDragFolderRingAnimator.animateToNaturalState();
3412 mDragFolderRingAnimator = null;
3413 }
3414 mFolderCreationAlarm.setOnAlarmListener(null);
3415 mFolderCreationAlarm.cancelAlarm();
3416 }
3417
3418 private void cleanupAddToFolder() {
3419 if (mDragOverFolderIcon != null) {
3420 mDragOverFolderIcon.onDragExit(null);
3421 mDragOverFolderIcon = null;
3422 }
3423 }
3424
3425 private void cleanupReorder(boolean cancelAlarm) {
3426 // Any pending reorders are canceled
3427 if (cancelAlarm) {
3428 mReorderAlarm.cancelAlarm();
3429 }
3430 mLastReorderX = -1;
3431 mLastReorderY = -1;
3432 }
3433
3434 /*
3435 *
3436 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3437 * coordinate space. The argument xy is modified with the return result.
3438 *
3439 * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3440 * computing it itself; we use this to avoid redundant matrix inversions in
3441 * findMatchingPageForDragOver
3442 *
3443 */
3444 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3445 xy[0] = xy[0] - v.getLeft();
3446 xy[1] = xy[1] - v.getTop();
3447 }
3448
3449 boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3450 if (r == null) {
3451 r = new Rect();
3452 }
3453 mTempPt[0] = x;
3454 mTempPt[1] = y;
3455 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3456
3457 LauncherAppState app = LauncherAppState.getInstance();
3458 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3459 r = grid.getHotseatRect();
3460 if (r.contains(mTempPt[0], mTempPt[1])) {
3461 return true;
3462 }
3463 return false;
3464 }
3465
3466 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3467 mTempPt[0] = (int) xy[0];
3468 mTempPt[1] = (int) xy[1];
3469 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3470 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3471
3472 xy[0] = mTempPt[0];
3473 xy[1] = mTempPt[1];
3474 }
3475
3476 /*
3477 *
3478 * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3479 * the parent View's coordinate space. The argument xy is modified with the return result.
3480 *
3481 */
3482 void mapPointFromChildToSelf(View v, float[] xy) {
3483 xy[0] += v.getLeft();
3484 xy[1] += v.getTop();
3485 }
3486
3487 static private float squaredDistance(float[] point1, float[] point2) {
3488 float distanceX = point1[0] - point2[0];
3489 float distanceY = point2[1] - point2[1];
3490 return distanceX * distanceX + distanceY * distanceY;
3491 }
3492
3493 /*
3494 *
3495 * This method returns the CellLayout that is currently being dragged to. In order to drag
3496 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3497 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3498 *
3499 * Return null if no CellLayout is currently being dragged over
3500 *
3501 */
3502 private CellLayout findMatchingPageForDragOver(
3503 DragView dragView, float originX, float originY, boolean exact) {
3504 // We loop through all the screens (ie CellLayouts) and see which ones overlap
3505 // with the item being dragged and then choose the one that's closest to the touch point
3506 final int screenCount = getChildCount();
3507 CellLayout bestMatchingScreen = null;
3508 float smallestDistSoFar = Float.MAX_VALUE;
3509
3510 for (int i = 0; i < screenCount; i++) {
3511 // The custom content screen is not a valid drag over option
3512 if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3513 continue;
3514 }
3515
3516 CellLayout cl = (CellLayout) getChildAt(i);
3517
3518 final float[] touchXy = {originX, originY};
3519 // Transform the touch coordinates to the CellLayout's local coordinates
3520 // If the touch point is within the bounds of the cell layout, we can return immediately
3521 cl.getMatrix().invert(mTempInverseMatrix);
3522 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3523
3524 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3525 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3526 return cl;
3527 }
3528
3529 if (!exact) {
3530 // Get the center of the cell layout in screen coordinates
3531 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3532 cellLayoutCenter[0] = cl.getWidth()/2;
3533 cellLayoutCenter[1] = cl.getHeight()/2;
3534 mapPointFromChildToSelf(cl, cellLayoutCenter);
3535
3536 touchXy[0] = originX;
3537 touchXy[1] = originY;
3538
3539 // Calculate the distance between the center of the CellLayout
3540 // and the touch point
3541 float dist = squaredDistance(touchXy, cellLayoutCenter);
3542
3543 if (dist < smallestDistSoFar) {
3544 smallestDistSoFar = dist;
3545 bestMatchingScreen = cl;
3546 }
3547 }
3548 }
3549 return bestMatchingScreen;
3550 }
3551
3552 // This is used to compute the visual center of the dragView. This point is then
3553 // used to visualize drop locations and determine where to drop an item. The idea is that
3554 // the visual center represents the user's interpretation of where the item is, and hence
3555 // is the appropriate point to use when determining drop location.
3556 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3557 DragView dragView, float[] recycle) {
3558 float res[];
3559 if (recycle == null) {
3560 res = new float[2];
3561 } else {
3562 res = recycle;
3563 }
3564
3565 // First off, the drag view has been shifted in a way that is not represented in the
3566 // x and y values or the x/yOffsets. Here we account for that shift.
3567 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3568 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3569
3570 // These represent the visual top and left of drag view if a dragRect was provided.
3571 // If a dragRect was not provided, then they correspond to the actual view left and
3572 // top, as the dragRect is in that case taken to be the entire dragView.
3573 // R.dimen.dragViewOffsetY.
3574 int left = x - xOffset;
3575 int top = y - yOffset;
3576
3577 // In order to find the visual center, we shift by half the dragRect
3578 res[0] = left + dragView.getDragRegion().width() / 2;
3579 res[1] = top + dragView.getDragRegion().height() / 2;
3580
3581 return res;
3582 }
3583
3584 private boolean isDragWidget(DragObject d) {
3585 return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3586 d.dragInfo instanceof PendingAddWidgetInfo);
3587 }
3588 private boolean isExternalDragWidget(DragObject d) {
3589 return d.dragSource != this && isDragWidget(d);
3590 }
3591
3592 public void onDragOver(DragObject d) {
3593 // Skip drag over events while we are dragging over side pages
3594 if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
3595
3596 Rect r = new Rect();
3597 CellLayout layout = null;
3598 ItemInfo item = (ItemInfo) d.dragInfo;
3599 if (item == null) {
3600 if (LauncherAppState.isDogfoodBuild()) {
3601 throw new NullPointerException("DragObject has null info");
3602 }
3603 return;
3604 }
3605
3606 // Ensure that we have proper spans for the item that we are dropping
3607 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3608 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
3609 d.dragView, mDragViewVisualCenter);
3610
3611 final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3612 // Identify whether we have dragged over a side page
3613 if (workspaceInModalState()) {
3614 if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
3615 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3616 layout = mLauncher.getHotseat().getLayout();
3617 }
3618 }
3619 if (layout == null) {
3620 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3621 }
3622 if (layout != mDragTargetLayout) {
3623 setCurrentDropLayout(layout);
3624 setCurrentDragOverlappingLayout(layout);
3625
3626 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3627 if (isInSpringLoadedMode) {
3628 if (mLauncher.isHotseatLayout(layout)) {
3629 mSpringLoadedDragController.cancel();
3630 } else {
3631 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3632 }
3633 }
3634 }
3635 } else {
3636 // Test to see if we are over the hotseat otherwise just use the current page
3637 if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3638 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3639 layout = mLauncher.getHotseat().getLayout();
3640 }
3641 }
3642 if (layout == null) {
3643 layout = getCurrentDropLayout();
3644 }
3645 if (layout != mDragTargetLayout) {
3646 setCurrentDropLayout(layout);
3647 setCurrentDragOverlappingLayout(layout);
3648 }
3649 }
3650
3651 // Handle the drag over
3652 if (mDragTargetLayout != null) {
3653 // We want the point to be mapped to the dragTarget.
3654 if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3655 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3656 } else {
3657 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3658 }
3659
3660 ItemInfo info = (ItemInfo) d.dragInfo;
3661
3662 int minSpanX = item.spanX;
3663 int minSpanY = item.spanY;
3664 if (item.minSpanX > 0 && item.minSpanY > 0) {
3665 minSpanX = item.minSpanX;
3666 minSpanY = item.minSpanY;
3667 }
3668
3669 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3670 (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3671 mDragTargetLayout, mTargetCell);
3672 int reorderX = mTargetCell[0];
3673 int reorderY = mTargetCell[1];
3674
3675 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3676
3677 float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3678 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3679
3680 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
3681 mTargetCell[1]);
3682
3683 manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
3684 targetCellDistance, dragOverView);
3685
3686 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3687 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3688 item.spanY, child, mTargetCell);
3689
3690 if (!nearestDropOccupied) {
3691 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3692 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3693 mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
3694 d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
3695 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3696 && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3697 mLastReorderY != reorderY)) {
3698
3699 int[] resultSpan = new int[2];
3700 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3701 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
3702 child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
3703
3704 // Otherwise, if we aren't adding to or creating a folder and there's no pending
3705 // reorder, then we schedule a reorder
3706 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3707 minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
3708 mReorderAlarm.setOnAlarmListener(listener);
3709 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3710 }
3711
3712 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3713 !nearestDropOccupied) {
3714 if (mDragTargetLayout != null) {
3715 mDragTargetLayout.revertTempState();
3716 }
3717 }
3718 }
3719 }
3720
3721 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3722 int[] targetCell, float distance, View dragOverView) {
3723 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3724 false);
3725
3726 if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3727 !mFolderCreationAlarm.alarmPending()) {
3728 mFolderCreationAlarm.setOnAlarmListener(new
3729 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3730 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3731 return;
3732 }
3733
3734 boolean willAddToFolder =
3735 willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3736
3737 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3738 mDragOverFolderIcon = ((FolderIcon) dragOverView);
3739 mDragOverFolderIcon.onDragEnter(info);
3740 if (targetLayout != null) {
3741 targetLayout.clearDragOutlines();
3742 }
3743 setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3744 return;
3745 }
3746
3747 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3748 setDragMode(DRAG_MODE_NONE);
3749 }
3750 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3751 setDragMode(DRAG_MODE_NONE);
3752 }
3753
3754 return;
3755 }
3756
3757 class FolderCreationAlarmListener implements OnAlarmListener {
3758 CellLayout layout;
3759 int cellX;
3760 int cellY;
3761
3762 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3763 this.layout = layout;
3764 this.cellX = cellX;
3765 this.cellY = cellY;
3766 }
3767
3768 public void onAlarm(Alarm alarm) {
3769 if (mDragFolderRingAnimator != null) {
3770 // This shouldn't happen ever, but just in case, make sure we clean up the mess.
3771 mDragFolderRingAnimator.animateToNaturalState();
3772 }
3773 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3774 mDragFolderRingAnimator.setCell(cellX, cellY);
3775 mDragFolderRingAnimator.setCellLayout(layout);
3776 mDragFolderRingAnimator.animateToAcceptState();
3777 layout.showFolderAccept(mDragFolderRingAnimator);
3778 layout.clearDragOutlines();
3779 setDragMode(DRAG_MODE_CREATE_FOLDER);
3780 }
3781 }
3782
3783 class ReorderAlarmListener implements OnAlarmListener {
3784 float[] dragViewCenter;
3785 int minSpanX, minSpanY, spanX, spanY;
3786 DragView dragView;
3787 View child;
3788
3789 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3790 int spanY, DragView dragView, View child) {
3791 this.dragViewCenter = dragViewCenter;
3792 this.minSpanX = minSpanX;
3793 this.minSpanY = minSpanY;
3794 this.spanX = spanX;
3795 this.spanY = spanY;
3796 this.child = child;
3797 this.dragView = dragView;
3798 }
3799
3800 public void onAlarm(Alarm alarm) {
3801 int[] resultSpan = new int[2];
3802 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3803 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3804 mTargetCell);
3805 mLastReorderX = mTargetCell[0];
3806 mLastReorderY = mTargetCell[1];
3807
3808 mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3809 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3810 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3811
3812 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3813 mDragTargetLayout.revertTempState();
3814 } else {
3815 setDragMode(DRAG_MODE_REORDER);
3816 }
3817
3818 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3819 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3820 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3821 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3822 dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3823 }
3824 }
3825
3826 @Override
3827 public void getHitRectRelativeToDragLayer(Rect outRect) {
3828 // We want the workspace to have the whole area of the display (it will find the correct
3829 // cell layout to drop to in the existing drag/drop logic.
3830 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3831 }
3832
3833 /**
3834 * Add the item specified by dragInfo to the given layout.
3835 * @return true if successful
3836 */
3837 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3838 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3839 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3840 return true;
3841 }
3842 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3843 return false;
3844 }
3845
3846 private void onDropExternal(int[] touchXY, Object dragInfo,
3847 CellLayout cellLayout, boolean insertAtFirst) {
3848 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3849 }
3850
3851 /**
3852 * Drop an item that didn't originate on one of the workspace screens.
3853 * It may have come from Launcher (e.g. from all apps or customize), or it may have
3854 * come from another app altogether.
3855 *
3856 * NOTE: This can also be called when we are outside of a drag event, when we want
3857 * to add an item to one of the workspace screens.
3858 */
3859 private void onDropExternal(final int[] touchXY, final Object dragInfo,
3860 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3861 final Runnable exitSpringLoadedRunnable = new Runnable() {
3862 @Override
3863 public void run() {
3864 mLauncher.exitSpringLoadedDragModeDelayed(true,
3865 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
3866 }
3867 };
3868
3869 ItemInfo info = (ItemInfo) dragInfo;
3870 int spanX = info.spanX;
3871 int spanY = info.spanY;
3872 if (mDragInfo != null) {
3873 spanX = mDragInfo.spanX;
3874 spanY = mDragInfo.spanY;
3875 }
3876
3877 final long container = mLauncher.isHotseatLayout(cellLayout) ?
3878 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3879 LauncherSettings.Favorites.CONTAINER_DESKTOP;
3880 final long screenId = getIdForScreen(cellLayout);
3881 if (!mLauncher.isHotseatLayout(cellLayout)
3882 && screenId != getScreenIdForPageIndex(mCurrentPage)
3883 && mState != State.SPRING_LOADED) {
3884 snapToScreenId(screenId, null);
3885 }
3886
3887 if (info instanceof PendingAddItemInfo) {
3888 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3889
3890 boolean findNearestVacantCell = true;
3891 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3892 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3893 cellLayout, mTargetCell);
3894 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3895 mDragViewVisualCenter[1], mTargetCell);
3896 if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3897 distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3898 cellLayout, mTargetCell, distance)) {
3899 findNearestVacantCell = false;
3900 }
3901 }
3902
3903 final ItemInfo item = (ItemInfo) d.dragInfo;
3904 boolean updateWidgetSize = false;
3905 if (findNearestVacantCell) {
3906 int minSpanX = item.spanX;
3907 int minSpanY = item.spanY;
3908 if (item.minSpanX > 0 && item.minSpanY > 0) {
3909 minSpanX = item.minSpanX;
3910 minSpanY = item.minSpanY;
3911 }
3912 int[] resultSpan = new int[2];
3913 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3914 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3915 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3916
3917 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3918 updateWidgetSize = true;
3919 }
3920 item.spanX = resultSpan[0];
3921 item.spanY = resultSpan[1];
3922 }
3923
3924 Runnable onAnimationCompleteRunnable = new Runnable() {
3925 @Override
3926 public void run() {
3927 // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
3928 // adding an item that may not be dropped right away (due to a config activity)
3929 // we defer the removal until the activity returns.
3930 deferRemoveExtraEmptyScreen();
3931
3932 // When dragging and dropping from customization tray, we deal with creating
3933 // widgets/shortcuts/folders in a slightly different way
3934 switch (pendingInfo.itemType) {
3935 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3936 int span[] = new int[2];
3937 span[0] = item.spanX;
3938 span[1] = item.spanY;
3939 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3940 container, screenId, mTargetCell, span, null);
3941 break;
3942 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3943 mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3944 container, screenId, mTargetCell, null);
3945 break;
3946 default:
3947 throw new IllegalStateException("Unknown item type: " +
3948 pendingInfo.itemType);
3949 }
3950 }
3951 };
3952 View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3953 ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3954
3955 if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3956 AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3957 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3958 item.spanY);
3959 }
3960
3961 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3962 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3963 ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3964 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3965 }
3966 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3967 animationStyle, finalView, true);
3968 } else {
3969 // This is for other drag/drop cases, like dragging from All Apps
3970 View view = null;
3971
3972 switch (info.itemType) {
3973 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3974 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3975 if (info.container == NO_ID && info instanceof AppInfo) {
3976 // Came from all apps -- make a copy
3977 info = new ShortcutInfo((AppInfo) info);
3978 }
3979 view = mLauncher.createShortcut(R.layout.application, cellLayout,
3980 (ShortcutInfo) info);
3981 break;
3982 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3983 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3984 (FolderInfo) info, mIconCache);
3985 break;
3986 default:
3987 throw new IllegalStateException("Unknown item type: " + info.itemType);
3988 }
3989
3990 // First we find the cell nearest to point at which the item is
3991 // dropped, without any consideration to whether there is an item there.
3992 if (touchXY != null) {
3993 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3994 cellLayout, mTargetCell);
3995 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3996 mDragViewVisualCenter[1], mTargetCell);
3997 d.postAnimationRunnable = exitSpringLoadedRunnable;
3998 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3999 true, d.dragView, d.postAnimationRunnable)) {
4000 return;
4001 }
4002 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
4003 true)) {
4004 return;
4005 }
4006 }
4007
4008 if (touchXY != null) {
4009 // when dragging and dropping, just find the closest free spot
4010 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
4011 (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
4012 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
4013 } else {
4014 cellLayout.findCellForSpan(mTargetCell, 1, 1);
4015 }
4016 // Add the item to DB before adding to screen ensures that the container and other
4017 // values of the info is properly updated.
4018 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
4019 mTargetCell[0], mTargetCell[1]);
4020
4021 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
4022 info.spanY, insertAtFirst);
4023 cellLayout.onDropChild(view);
4024 cellLayout.getShortcutsAndWidgets().measureChild(view);
4025
4026 if (d.dragView != null) {
4027 // We wrap the animation call in the temporary set and reset of the current
4028 // cellLayout to its final transform -- this means we animate the drag view to
4029 // the correct final location.
4030 setFinalTransitionTransform(cellLayout);
4031 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
4032 exitSpringLoadedRunnable, this);
4033 resetTransitionTransform(cellLayout);
4034 }
4035 }
4036 }
4037
4038 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
4039 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
4040 widgetInfo.spanY, widgetInfo, false);
4041 int visibility = layout.getVisibility();
4042 layout.setVisibility(VISIBLE);
4043
4044 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
4045 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
4046 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
4047 Bitmap.Config.ARGB_8888);
4048 mCanvas.setBitmap(b);
4049
4050 layout.measure(width, height);
4051 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
4052 layout.draw(mCanvas);
4053 mCanvas.setBitmap(null);
4054 layout.setVisibility(visibility);
4055 return b;
4056 }
4057
4058 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
4059 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
4060 boolean external, boolean scale) {
4061 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
4062 // location and size on the home screen.
4063 int spanX = info.spanX;
4064 int spanY = info.spanY;
4065
4066 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
4067 loc[0] = r.left;
4068 loc[1] = r.top;
4069
4070 setFinalTransitionTransform(layout);
4071 float cellLayoutScale =
4072 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
4073 resetTransitionTransform(layout);
4074
4075 float dragViewScaleX;
4076 float dragViewScaleY;
4077 if (scale) {
4078 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
4079 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
4080 } else {
4081 dragViewScaleX = 1f;
4082 dragViewScaleY = 1f;
4083 }
4084
4085 // The animation will scale the dragView about its center, so we need to center about
4086 // the final location.
4087 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
4088 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
4089
4090 scaleXY[0] = dragViewScaleX * cellLayoutScale;
4091 scaleXY[1] = dragViewScaleY * cellLayoutScale;
4092 }
4093
4094 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
4095 final Runnable onCompleteRunnable, int animationType, final View finalView,
4096 boolean external) {
4097 Rect from = new Rect();
4098 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
4099
4100 int[] finalPos = new int[2];
4101 float scaleXY[] = new float[2];
4102 boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
4103 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
4104 external, scalePreview);
4105
4106 Resources res = mLauncher.getResources();
4107 final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
4108
4109 // In the case where we've prebound the widget, we remove it from the DragLayer
4110 if (finalView instanceof AppWidgetHostView && external) {
4111 Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
4112 mLauncher.getDragLayer().removeView(finalView);
4113 }
4114 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
4115 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
4116 dragView.setCrossFadeBitmap(crossFadeBitmap);
4117 dragView.crossFade((int) (duration * 0.8f));
4118 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
4119 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
4120 }
4121
4122 DragLayer dragLayer = mLauncher.getDragLayer();
4123 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
4124 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
4125 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
4126 } else {
4127 int endStyle;
4128 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
4129 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
4130 } else {
4131 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
4132 }
4133
4134 Runnable onComplete = new Runnable() {
4135 @Override
4136 public void run() {
4137 if (finalView != null) {
4138 finalView.setVisibility(VISIBLE);
4139 }
4140 if (onCompleteRunnable != null) {
4141 onCompleteRunnable.run();
4142 }
4143 }
4144 };
4145 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
4146 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
4147 duration, this);
4148 }
4149 }
4150
4151 public void setFinalTransitionTransform(CellLayout layout) {
4152 if (isSwitchingState()) {
4153 mCurrentScale = getScaleX();
4154 setScaleX(mNewScale);
4155 setScaleY(mNewScale);
4156 }
4157 }
4158 public void resetTransitionTransform(CellLayout layout) {
4159 if (isSwitchingState()) {
4160 setScaleX(mCurrentScale);
4161 setScaleY(mCurrentScale);
4162 }
4163 }
4164
4165 /**
4166 * Return the current {@link CellLayout}, correctly picking the destination
4167 * screen while a scroll is in progress.
4168 */
4169 public CellLayout getCurrentDropLayout() {
4170 return (CellLayout) getChildAt(getNextPage());
4171 }
4172
4173 /**
4174 * Return the current CellInfo describing our current drag; this method exists
4175 * so that Launcher can sync this object with the correct info when the activity is created/
4176 * destroyed
4177 *
4178 */
4179 public CellLayout.CellInfo getDragInfo() {
4180 return mDragInfo;
4181 }
4182
4183 public int getCurrentPageOffsetFromCustomContent() {
4184 return getNextPage() - numCustomPages();
4185 }
4186
4187 /**
4188 * Calculate the nearest cell where the given object would be dropped.
4189 *
4190 * pixelX and pixelY should be in the coordinate system of layout
4191 */
4192 private int[] findNearestArea(int pixelX, int pixelY,
4193 int spanX, int spanY, CellLayout layout, int[] recycle) {
4194 return layout.findNearestArea(
4195 pixelX, pixelY, spanX, spanY, recycle);
4196 }
4197
4198 void setup(DragController dragController) {
4199 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
4200 mDragController = dragController;
4201
4202 // hardware layers on children are enabled on startup, but should be disabled until
4203 // needed
4204 updateChildrenLayersEnabled(false);
4205 }
4206
4207 /**
4208 * Called at the end of a drag which originated on the workspace.
4209 */
4210 public void onDropCompleted(final View target, final DragObject d,
4211 final boolean isFlingToDelete, final boolean success) {
4212 if (mDeferDropAfterUninstall) {
4213 mDeferredAction = new Runnable() {
4214 public void run() {
4215 onDropCompleted(target, d, isFlingToDelete, success);
4216 mDeferredAction = null;
4217 }
4218 };
4219 return;
4220 }
4221
4222 boolean beingCalledAfterUninstall = mDeferredAction != null;
4223
4224 if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
4225 if (target != this && mDragInfo != null) {
4226 CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
4227 if (parentCell != null) {
4228 parentCell.removeView(mDragInfo.cell);
4229 } else if (LauncherAppState.isDogfoodBuild()) {
4230 throw new NullPointerException("mDragInfo.cell has null parent");
4231 }
4232 if (mDragInfo.cell instanceof DropTarget) {
4233 mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
4234 }
4235 }
4236 } else if (mDragInfo != null) {
4237 CellLayout cellLayout;
4238 if (mLauncher.isHotseatLayout(target)) {
4239 cellLayout = mLauncher.getHotseat().getLayout();
4240 } else {
4241 cellLayout = getScreenWithId(mDragInfo.screenId);
4242 }
4243 if (cellLayout == null && LauncherAppState.isDogfoodBuild()) {
4244 throw new RuntimeException("Invalid state: cellLayout == null in "
4245 + "Workspace#onDropCompleted. Please file a bug. ");
4246 }
4247 if (cellLayout != null) {
4248 cellLayout.onDropChild(mDragInfo.cell);
4249 }
4250 }
4251 if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
4252 && mDragInfo.cell != null) {
4253 mDragInfo.cell.setVisibility(VISIBLE);
4254 }
4255 mDragOutline = null;
4256 mDragInfo = null;
4257 }
4258
4259 public void deferCompleteDropAfterUninstallActivity() {
4260 mDeferDropAfterUninstall = true;
4261 }
4262
4263 /// maybe move this into a smaller part
4264 public void onUninstallActivityReturned(boolean success) {
4265 mDeferDropAfterUninstall = false;
4266 mUninstallSuccessful = success;
4267 if (mDeferredAction != null) {
4268 mDeferredAction.run();
4269 }
4270 }
4271
4272 void updateItemLocationsInDatabase(CellLayout cl) {
4273 int count = cl.getShortcutsAndWidgets().getChildCount();
4274
4275 long screenId = getIdForScreen(cl);
4276 int container = Favorites.CONTAINER_DESKTOP;
4277
4278 if (mLauncher.isHotseatLayout(cl)) {
4279 screenId = -1;
4280 container = Favorites.CONTAINER_HOTSEAT;
4281 }
4282
4283 for (int i = 0; i < count; i++) {
4284 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4285 ItemInfo info = (ItemInfo) v.getTag();
4286 // Null check required as the AllApps button doesn't have an item info
4287 if (info != null && info.requiresDbUpdate) {
4288 info.requiresDbUpdate = false;
4289 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
4290 info.cellY, info.spanX, info.spanY);
4291 }
4292 }
4293 }
4294
4295 ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplic🔵
4296 ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
4297 getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, fals🔵
4298 int count = getChildCount();
4299 for (int i = 0; i < count; i++) {
4300 CellLayout cl = (CellLayout) getChildAt(i);
4301 getUniqueIntents(cl, uniqueIntents, duplicates, false);
4302 }
4303 return uniqueIntents;
4304 }
4305
4306 void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
4307 ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
4308 int count = cl.getShortcutsAndWidgets().getChildCount();
4309
4310 ArrayList<View> children = new ArrayList<View>();
4311 for (int i = 0; i < count; i++) {
4312 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4313 children.add(v);
4314 }
4315
4316 for (int i = 0; i < count; i++) {
4317 View v = children.get(i);
4318 ItemInfo info = (ItemInfo) v.getTag();
4319 // Null check required as the AllApps button doesn't have an item info
4320 if (info instanceof ShortcutInfo) {
4321 ShortcutInfo si = (ShortcutInfo) info;
4322 ComponentName cn = si.intent.getComponent();
4323
4324 Uri dataUri = si.intent.getData();
4325 // If dataUri is not null / empty or if this component isn't one that would
4326 // have previously showed up in the AllApps list, then this is a widget-type
4327 // shortcut, so ignore it.
4328 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4329 continue;
4330 }
4331
4332 if (!uniqueIntents.contains(cn)) {
4333 uniqueIntents.add(cn);
4334 } else {
4335 if (stripDuplicates) {
4336 cl.removeViewInLayout(v);
4337 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4338 }
4339 if (duplicates != null) {
4340 duplicates.add(cn);
4341 }
4342 }
4343 }
4344 if (v instanceof FolderIcon) {
4345 FolderIcon fi = (FolderIcon) v;
4346 ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
4347 for (int j = 0; j < items.size(); j++) {
4348 if (items.get(j).getTag() instanceof ShortcutInfo) {
4349 ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
4350 ComponentName cn = si.intent.getComponent();
4351
4352 Uri dataUri = si.intent.getData();
4353 // If dataUri is not null / empty or if this component isn't one that would
4354 // have previously showed up in the AllApps list, then this is a widget-type
4355 // shortcut, so ignore it.
4356 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4357 continue;
4358 }
4359
4360 if (!uniqueIntents.contains(cn)) {
4361 uniqueIntents.add(cn);
4362 } else {
4363 if (stripDuplicates) {
4364 fi.getFolderInfo().remove(si);
4365 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4366 }
4367 if (duplicates != null) {
4368 duplicates.add(cn);
4369 }
4370 }
4371 }
4372 }
4373 }
4374 }
4375 }
4376
4377 void saveWorkspaceToDb() {
4378 saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
4379 int count = getChildCount();
4380 for (int i = 0; i < count; i++) {
4381 CellLayout cl = (CellLayout) getChildAt(i);
4382 saveWorkspaceScreenToDb(cl);
4383 }
4384 }
4385
4386 void saveWorkspaceScreenToDb(CellLayout cl) {
4387 int count = cl.getShortcutsAndWidgets().getChildCount();
4388
4389 long screenId = getIdForScreen(cl);
4390 int container = Favorites.CONTAINER_DESKTOP;
4391
4392 Hotseat hotseat = mLauncher.getHotseat();
4393 if (mLauncher.isHotseatLayout(cl)) {
4394 screenId = -1;
4395 container = Favorites.CONTAINER_HOTSEAT;
4396 }
4397
4398 for (int i = 0; i < count; i++) {
4399 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4400 ItemInfo info = (ItemInfo) v.getTag();
4401 // Null check required as the AllApps button doesn't have an item info
4402 if (info != null) {
4403 int cellX = info.cellX;
4404 int cellY = info.cellY;
4405 if (container == Favorites.CONTAINER_HOTSEAT) {
4406 cellX = hotseat.getCellXFromOrder((int) info.screenId);
4407 cellY = hotseat.getCellYFromOrder((int) info.screenId);
4408 }
4409 LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4410 cellY, false);
4411 }
4412 if (v instanceof FolderIcon) {
4413 FolderIcon fi = (FolderIcon) v;
4414 fi.getFolder().addItemLocationsInDatabase();
4415 }
4416 }
4417 }
4418
4419 @Override
4420 public float getIntrinsicIconScaleFactor() {
4421 return 1f;
4422 }
4423
4424 @Override
4425 public boolean supportsFlingToDelete() {
4426 return true;
4427 }
4428
4429 @Override
4430 public boolean supportsAppInfoDropTarget() {
4431 return false;
4432 }
4433
4434 @Override
4435 public boolean supportsDeleteDropTarget() {
4436 return true;
4437 }
4438
4439 @Override
4440 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4441 // Do nothing
4442 }
4443
4444 @Override
4445 public void onFlingToDeleteCompleted() {
4446 // Do nothing
4447 }
4448
4449 public boolean isDropEnabled() {
4450 return true;
4451 }
4452
4453 @Override
4454 protected void onRestoreInstanceState(Parcelable state) {
4455 super.onRestoreInstanceState(state);
4456 Launcher.setScreen(mCurrentPage);
4457 }
4458
4459 @Override
4460 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4461 // We don't dispatch restoreInstanceState to our children using this code path.
4462 // Some pages will be restored immediately as their items are bound immediately, and
4463 // others we will need to wait until after their items are bound.
4464 mSavedStates = container;
4465 }
4466
4467 public void restoreInstanceStateForChild(int child) {
4468 if (mSavedStates != null) {
4469 mRestoredPages.add(child);
4470 CellLayout cl = (CellLayout) getChildAt(child);
4471 if (cl != null) {
4472 cl.restoreInstanceState(mSavedStates);
4473 }
4474 }
4475 }
4476
4477 public void restoreInstanceStateForRemainingPages() {
4478 int count = getChildCount();
4479 for (int i = 0; i < count; i++) {
4480 if (!mRestoredPages.contains(i)) {
4481 restoreInstanceStateForChild(i);
4482 }
4483 }
4484 mRestoredPages.clear();
4485 mSavedStates = null;
4486 }
4487
4488 @Override
4489 public void scrollLeft() {
4490 if (!workspaceInModalState() && !mIsSwitchingState) {
4491 super.scrollLeft();
4492 }
4493 Folder openFolder = getOpenFolder();
4494 if (openFolder != null) {
4495 openFolder.completeDragExit();
4496 }
4497 }
4498
4499 @Override
4500 public void scrollRight() {
4501 if (!workspaceInModalState() && !mIsSwitchingState) {
4502 super.scrollRight();
4503 }
4504 Folder openFolder = getOpenFolder();
4505 if (openFolder != null) {
4506 openFolder.completeDragExit();
4507 }
4508 }
4509
4510 @Override
4511 public boolean onEnterScrollArea(int x, int y, int direction) {
4512 // Ignore the scroll area if we are dragging over the hot seat
4513 boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4514 if (mLauncher.getHotseat() != null && isPortrait) {
4515 Rect r = new Rect();
4516 mLauncher.getHotseat().getHitRect(r);
4517 if (r.contains(x, y)) {
4518 return false;
4519 }
4520 }
4521
4522 boolean result = false;
4523 if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
4524 mInScrollArea = true;
4525
4526 final int page = getNextPage() +
4527 (direction == DragController.SCROLL_LEFT ? -1 : 1);
4528 // We always want to exit the current layout to ensure parity of enter / exit
4529 setCurrentDropLayout(null);
4530
4531 if (0 <= page && page < getChildCount()) {
4532 // Ensure that we are not dragging over to the custom content screen
4533 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4534 return false;
4535 }
4536
4537 CellLayout layout = (CellLayout) getChildAt(page);
4538 setCurrentDragOverlappingLayout(layout);
4539
4540 // Workspace is responsible for drawing the edge glow on adjacent pages,
4541 // so we need to redraw the workspace when this may have changed.
4542 invalidate();
4543 result = true;
4544 }
4545 }
4546 return result;
4547 }
4548
4549 @Override
4550 public boolean onExitScrollArea() {
4551 boolean result = false;
4552 if (mInScrollArea) {
4553 invalidate();
4554 CellLayout layout = getCurrentDropLayout();
4555 setCurrentDropLayout(layout);
4556 setCurrentDragOverlappingLayout(layout);
4557
4558 result = true;
4559 mInScrollArea = false;
4560 }
4561 return result;
4562 }
4563
4564 private void onResetScrollArea() {
4565 setCurrentDragOverlappingLayout(null);
4566 mInScrollArea = false;
4567 }
4568
4569 /**
4570 * Returns a specific CellLayout
4571 */
4572 CellLayout getParentCellLayoutForView(View v) {
4573 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4574 for (CellLayout layout : layouts) {
4575 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4576 return layout;
4577 }
4578 }
4579 return null;
4580 }
4581
4582 /**
4583 * Returns a list of all the CellLayouts in the workspace.
4584 */
4585 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4586 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4587 int screenCount = getChildCount();
4588 for (int screen = 0; screen < screenCount; screen++) {
4589 layouts.add(((CellLayout) getChildAt(screen)));
4590 }
4591 if (mLauncher.getHotseat() != null) {
4592 layouts.add(mLauncher.getHotseat().getLayout());
4593 }
4594 return layouts;
4595 }
4596
4597 /**
4598 * We should only use this to search for specific children. Do not use this method to modify
4599 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4600 * the hotseat and workspace pages
4601 */
4602 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4603 ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4604 new ArrayList<ShortcutAndWidgetContainer>();
4605 int screenCount = getChildCount();
4606 for (int screen = 0; screen < screenCount; screen++) {
4607 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4608 }
4609 if (mLauncher.getHotseat() != null) {
4610 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4611 }
4612 return childrenLayouts;
4613 }
4614
4615 public Folder getFolderForTag(final Object tag) {
4616 return (Folder) getFirstMatch(new ItemOperator() {
4617
4618 @Override
4619 public boolean evaluate(ItemInfo info, View v, View parent) {
4620 return (v instanceof Folder) && (((Folder) v).getInfo() == tag)
4621 && ((Folder) v).getInfo().opened;
4622 }
4623 });
4624 }
4625
4626 public View getViewForTag(final Object tag) {
4627 return getFirstMatch(new ItemOperator() {
4628
4629 @Override
4630 public boolean evaluate(ItemInfo info, View v, View parent) {
4631 return info == tag;
4632 }
4633 });
4634 }
4635
4636 public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
4637 return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
4638
4639 @Override
4640 public boolean evaluate(ItemInfo info, View v, View parent) {
4641 return (info instanceof LauncherAppWidgetInfo) &&
4642 ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
4643 }
4644 });
4645 }
4646
4647 private View getFirstMatch(final ItemOperator operator) {
4648 final View[] value = new View[1];
4649 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4650 @Override
4651 public boolean evaluate(ItemInfo info, View v, View parent) {
4652 if (operator.evaluate(info, v, parent)) {
4653 value[0] = v;
4654 return true;
4655 }
4656 return false;
4657 }
4658 });
4659 return value[0];
4660 }
4661
4662 void clearDropTargets() {
4663 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4664 @Override
4665 public boolean evaluate(ItemInfo info, View v, View parent) {
4666 if (v instanceof DropTarget) {
4667 mDragController.removeDropTarget((DropTarget) v);
4668 }
4669 // not done, process all the shortcuts
4670 return false;
4671 }
4672 });
4673 }
4674
4675 // Removes ALL items that match a given package name, this is usually called when a package
4676 // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4677 // belong to that package.
4678 void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
4679 final HashSet<String> packageNames = new HashSet<String>();
4680 packageNames.addAll(packages);
4681
4682 // Filter out all the ItemInfos that this is going to affect
4683 final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4684 final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4685 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4686 for (CellLayout layoutParent : cellLayouts) {
4687 ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4688 int childCount = layout.getChildCount();
4689 for (int i = 0; i < childCount; ++i) {
4690 View view = layout.getChildAt(i);
4691 infos.add((ItemInfo) view.getTag());
4692 }
4693 }
4694 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4695 @Override
4696 public boolean filterItem(ItemInfo parent, ItemInfo info,
4697 ComponentName cn) {
4698 if (packageNames.contains(cn.getPackageName())
4699 && info.user.equals(user)) {
4700 cns.add(cn);
4701 return true;
4702 }
4703 return false;
4704 }
4705 };
4706 LauncherModel.filterItemInfos(infos, filter);
4707
4708 // Remove the affected components
4709 removeItemsByComponentName(cns, user);
4710 }
4711
4712 // Removes items that match the application info specified, when applications are removed
4713 // as a part of an update, this is called to ensure that other widgets and application
4714 // shortcuts are not removed.
4715 void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos, UserHandleCompat user) {
4716 // Just create a hash table of all the specific components that this will affect
4717 HashSet<ComponentName> cns = new HashSet<ComponentName>();
4718 for (AppInfo info : appInfos) {
4719 cns.add(info.componentName);
4720 }
4721
4722 // Remove all the things
4723 removeItemsByComponentName(cns, user);
4724 }
4725
4726 void removeItemsByComponentName(final HashSet<ComponentName> componentNames,
4727 final UserHandleCompat user) {
4728 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4729 for (final CellLayout layoutParent: cellLayouts) {
4730 final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4731
4732 final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4733 for (int j = 0; j < layout.getChildCount(); j++) {
4734 final View view = layout.getChildAt(j);
4735 children.put((ItemInfo) view.getTag(), view);
4736 }
4737
4738 final ArrayList<View> childrenToRemove = new ArrayList<View>();
4739 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
4740 new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4741 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4742 @Override
4743 public boolean filterItem(ItemInfo parent, ItemInfo info,
4744 ComponentName cn) {
4745 if (parent instanceof FolderInfo) {
4746 if (componentNames.contains(cn) && info.user.equals(user)) {
4747 FolderInfo folder = (FolderInfo) parent;
4748 ArrayList<ShortcutInfo> appsToRemove;
4749 if (folderAppsToRemove.containsKey(folder)) {
4750 appsToRemove = folderAppsToRemove.get(folder);
4751 } else {
4752 appsToRemove = new ArrayList<ShortcutInfo>();
4753 folderAppsToRemove.put(folder, appsToRemove);
4754 }
4755 appsToRemove.add((ShortcutInfo) info);
4756 return true;
4757 }
4758 } else {
4759 if (componentNames.contains(cn) && info.user.equals(user)) {
4760 childrenToRemove.add(children.get(info));
4761 return true;
4762 }
4763 }
4764 return false;
4765 }
4766 };
4767 LauncherModel.filterItemInfos(children.keySet(), filter);
4768
4769 // Remove all the apps from their folders
4770 for (FolderInfo folder : folderAppsToRemove.keySet()) {
4771 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4772 for (ShortcutInfo info : appsToRemove) {
4773 folder.remove(info);
4774 }
4775 }
4776
4777 // Remove all the other children
4778 for (View child : childrenToRemove) {
4779 // Note: We can not remove the view directly from CellLayoutChildren as this
4780 // does not re-mark the spaces as unoccupied.
4781 layoutParent.removeViewInLayout(child);
4782 if (child instanceof DropTarget) {
4783 mDragController.removeDropTarget((DropTarget) child);
4784 }
4785 }
4786
4787 if (childrenToRemove.size() > 0) {
4788 layout.requestLayout();
4789 layout.invalidate();
4790 }
4791 }
4792
4793 // Strip all the empty screens
4794 stripEmptyScreens();
4795 }
4796
4797 interface ItemOperator {
4798 /**
4799 * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
4800 *
4801 * @param info info for the shortcut
4802 * @param view view for the shortcut
4803 * @param parent containing folder, or null
4804 * @return true if done, false to continue the map
4805 */
4806 public boolean evaluate(ItemInfo info, View view, View parent);
4807 }
4808
4809 /**
4810 * Map the operator over the shortcuts and widgets, return the first-non-null value.
4811 *
4812 * @param recurse true: iterate over folder children. false: op get the folders themselves.
4813 * @param op the operator to map over the shortcuts
4814 */
4815 void mapOverItems(boolean recurse, ItemOperator op) {
4816 ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
4817 final int containerCount = containers.size();
4818 for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
4819 ShortcutAndWidgetContainer container = containers.get(containerIdx);
4820 // map over all the shortcuts on the workspace
4821 final int itemCount = container.getChildCount();
4822 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
4823 View item = container.getChildAt(itemIdx);
4824 ItemInfo info = (ItemInfo) item.getTag();
4825 if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
4826 FolderIcon folder = (FolderIcon) item;
4827 ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4828 // map over all the children in the folder
4829 final int childCount = folderChildren.size();
4830 for (int childIdx = 0; childIdx < childCount; childIdx++) {
4831 View child = folderChildren.get(childIdx);
4832 info = (ItemInfo) child.getTag();
4833 if (op.evaluate(info, child, folder)) {
4834 return;
4835 }
4836 }
4837 } else {
4838 if (op.evaluate(info, item, null)) {
4839 return;
4840 }
4841 }
4842 }
4843 }
4844 }
4845
4846 void updateShortcutsAndWidgets(ArrayList<AppInfo> apps) {
4847 // Break the appinfo list per user
4848 final HashMap<UserHandleCompat, ArrayList<AppInfo>> appsPerUser =
4849 new HashMap<UserHandleCompat, ArrayList<AppInfo>>();
4850 for (AppInfo info : apps) {
4851 ArrayList<AppInfo> filtered = appsPerUser.get(info.user);
4852 if (filtered == null) {
4853 filtered = new ArrayList<AppInfo>();
4854 appsPerUser.put(info.user, filtered);
4855 }
4856 filtered.add(info);
4857 }
4858
4859 for (Map.Entry<UserHandleCompat, ArrayList<AppInfo>> entry : appsPerUser.entrySet()) {
4860 updateShortcutsAndWidgetsPerUser(entry.getValue(), entry.getKey());
4861 }
4862 }
4863
4864 private void updateShortcutsAndWidgetsPerUser(ArrayList<AppInfo> apps,
4865 final UserHandleCompat user) {
4866 // Create a map of the apps to test against
4867 final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
4868 final HashSet<String> pkgNames = new HashSet<String>();
4869 for (AppInfo ai : apps) {
4870 appsMap.put(ai.componentName, ai);
4871 pkgNames.add(ai.componentName.getPackageName());
4872 }
4873 final HashSet<ComponentName> iconsToRemove = new HashSet<ComponentName>();
4874
4875 mapOverItems(MAP_RECURSE, new ItemOperator() {
4876 @Override
4877 public boolean evaluate(ItemInfo info, View v, View parent) {
4878 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4879 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4880 ComponentName cn = shortcutInfo.getTargetComponent();
4881 AppInfo appInfo = appsMap.get(cn);
4882 if (user.equals(shortcutInfo.user) && cn != null
4883 && LauncherModel.isShortcutInfoUpdateable(info)
4884 && pkgNames.contains(cn.getPackageName())) {
4885 boolean promiseStateChanged = false;
4886 boolean infoUpdated = false;
4887 if (shortcutInfo.isPromise()) {
4888 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4889 // Auto install icon
4890 PackageManager pm = getContext().getPackageManager();
4891 ResolveInfo matched = pm.resolveActivity(
4892 new Intent(Intent.ACTION_MAIN)
4893 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
4894 PackageManager.MATCH_DEFAULT_ONLY);
4895 if (matched == null) {
4896 // Try to find the best match activity.
4897 Intent intent = pm.getLaunchIntentForPackage(
4898 cn.getPackageName());
4899 if (intent != null) {
4900 cn = intent.getComponent();
4901 appInfo = appsMap.get(cn);
4902 }
4903
4904 if ((intent == null) || (appsMap == null)) {
4905 // Could not find a default activity. Remove this item.
4906 iconsToRemove.add(shortcutInfo.getTargetComponent());
4907
4908 // process next shortcut.
4909 return false;
4910 }
4911 shortcutInfo.promisedIntent = intent;
4912 }
4913 }
4914
4915 // Restore the shortcut.
4916 shortcutInfo.intent = shortcutInfo.promisedIntent;
4917 shortcutInfo.promisedIntent = null;
4918 shortcutInfo.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
4919 & ~ShortcutInfo.FLAG_AUTOINTALL_ICON
4920 & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
4921
4922 promiseStateChanged = true;
4923 infoUpdated = true;
4924 shortcutInfo.updateIcon(mIconCache);
4925 LauncherModel.updateItemInDatabase(getContext(), shortcutInfo);
4926 }
4927
4928
4929 if (appInfo != null) {
4930 shortcutInfo.updateIcon(mIconCache);
4931 shortcutInfo.title = appInfo.title.toString();
4932 shortcutInfo.contentDescription = appInfo.contentDescription;
4933 infoUpdated = true;
4934 }
4935
4936 if (infoUpdated) {
4937 BubbleTextView shortcut = (BubbleTextView) v;
4938 shortcut.applyFromShortcutInfo(shortcutInfo,
4939 mIconCache, true, promiseStateChanged);
4940
4941 if (parent != null) {
4942 parent.invalidate();
4943 }
4944 }
4945 }
4946 }
4947 // process all the shortcuts
4948 return false;
4949 }
4950 });
4951
4952 if (!iconsToRemove.isEmpty()) {
4953 removeItemsByComponentName(iconsToRemove, user);
4954 }
4955 if (user.equals(UserHandleCompat.myUserHandle())) {
4956 restorePendingWidgets(pkgNames);
4957 }
4958 }
4959
4960 public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
4961 ArrayList<String> packages = new ArrayList<String>(1);
4962 packages.add(packageName);
4963 LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
4964 removeItemsByPackageName(packages, user);
4965 }
4966
4967 public void updatePackageBadge(final String packageName, final UserHandleCompat user) {
4968 mapOverItems(MAP_RECURSE, new ItemOperator() {
4969 @Override
4970 public boolean evaluate(ItemInfo info, View v, View parent) {
4971 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4972 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4973 ComponentName cn = shortcutInfo.getTargetComponent();
4974 if (user.equals(shortcutInfo.user) && cn != null
4975 && shortcutInfo.isPromise()
4976 && packageName.equals(cn.getPackageName())) {
4977 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4978 // For auto install apps update the icon as well as label.
4979 mIconCache.getTitleAndIcon(shortcutInfo,
4980 shortcutInfo.promisedIntent, user, true);
4981 } else {
4982 // Only update the icon for restored apps.
4983 shortcutInfo.updateIcon(mIconCache);
4984 }
4985 BubbleTextView shortcut = (BubbleTextView) v;
4986 shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
4987
4988 if (parent != null) {
4989 parent.invalidate();
4990 }
4991 }
4992 }
4993 // process all the shortcuts
4994 return false;
4995 }
4996 });
4997 }
4998
4999 public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {
5000 HashSet<String> completedPackages = new HashSet<String>();
5001
5002 for (final PackageInstallInfo installInfo : installInfos) {
5003 mapOverItems(MAP_RECURSE, new ItemOperator() {
5004 @Override
5005 public boolean evaluate(ItemInfo info, View v, View parent) {
5006 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
5007 ShortcutInfo si = (ShortcutInfo) info;
5008 ComponentName cn = si.getTargetComponent();
5009 if (si.isPromise() && (cn != null)
5010 && installInfo.packageName.equals(cn.getPackageName())) {
5011 si.setInstallProgress(installInfo.progress);
5012 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
5013 // Mark this info as broken.
5014 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
5015 }
5016 ((BubbleTextView)v).applyState(false);
5017 }
5018 } else if (v instanceof PendingAppWidgetHostView
5019 && info instanceof LauncherAppWidgetInfo
5020 && ((LauncherAppWidgetInfo) info).providerName.getPackageName()
5021 .equals(installInfo.packageName)) {
5022 ((LauncherAppWidgetInfo) info).installProgress = installInfo.progress;
5023 ((PendingAppWidgetHostView) v).applyState();
5024 }
5025
5026 // process all the shortcuts
5027 return false;
5028 }
5029 });
5030
5031 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
5032 completedPackages.add(installInfo.packageName);
5033 }
5034 }
5035
5036 // Note that package states are sent only for myUser
5037 if (!completedPackages.isEmpty()) {
5038 restorePendingWidgets(completedPackages);
5039 }
5040 }
5041
5042 private void restorePendingWidgets(final Set<String> installedPackaged) {
5043 final ArrayList<LauncherAppWidgetInfo> changedInfo = new ArrayList<LauncherAppWidgetInfo>();
5044
5045 // Iterate non recursively as widgets can't be inside a folder.
5046 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
5047
5048 @Override
5049 public boolean evaluate(ItemInfo info, View v, View parent) {
5050 if (info instanceof LauncherAppWidgetInfo) {
5051 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
5052 if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
5053 && installedPackaged.contains(widgetInfo.providerName.getPackageName())) {
5054
5055 changedInfo.add(widgetInfo);
5056
5057 // Remove the provider not ready flag
5058 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
5059 LauncherModel.updateItemInDatabase(getContext(), widgetInfo);
5060 }
5061 }
5062 // process all the widget
5063 return false;
5064 }
5065 });
5066 if (!changedInfo.isEmpty()) {
5067 DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
5068 mLauncher.getAppWidgetHost());
5069 if (LauncherModel.findAppWidgetProviderInfoWithComponent(getContext(),
5070 changedInfo.get(0).providerName) != null) {
5071 // Re-inflate the widgets which have changed status
5072 widgetRefresh.run();
5073 } else {
5074 // widgetRefresh will automatically run when the packages are updated.
5075 }
5076 }
5077 }
5078
5079 private void moveToScreen(int page, boolean animate) {
5080 if (!workspaceInModalState()) {
5081 if (animate) {
5082 snapToPage(page);
5083 } else {
5084 setCurrentPage(page);
5085 }
5086 }
5087 View child = getChildAt(page);
5088 if (child != null) {
5089 child.requestFocus();
5090 }
5091 }
5092
5093 void moveToDefaultScreen(boolean animate) {
5094 moveToScreen(mDefaultPage, animate);
5095 }
5096
5097 void moveToCustomContentScreen(boolean animate) {
5098 if (hasCustomContent()) {
5099 int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
5100 if (animate) {
5101 snapToPage(ccIndex);
5102 } else {
5103 setCurrentPage(ccIndex);
5104 }
5105 View child = getChildAt(ccIndex);
5106 if (child != null) {
5107 child.requestFocus();
5108 }
5109 }
5110 exitWidgetResizeMode();
5111 }
5112
5113 @Override
5114 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
5115 long screenId = getScreenIdForPageIndex(pageIndex);
5116 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
5117 int count = mScreenOrder.size() - numCustomPages();
5118 if (count > 1) {
5119 return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
5120 R.drawable.ic_pageindicator_add);
5121 }
5122 }
5123
5124 return super.getPageIndicatorMarker(pageIndex);
5125 }
5126
5127 @Override
5128 public void syncPages() {
5129 }
5130
5131 @Override
5132 public void syncPageItems(int page, boolean immediate) {
5133 }
5134
5135 protected String getPageIndicatorDescription() {
5136 String settings = getResources().getString(R.string.settings_button_text);
5137 return getCurrentPageDescription() + ", " + settings;
5138 }
5139
5140 protected String getCurrentPageDescription() {
5141 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
5142 int delta = numCustomPages();
5143 if (hasCustomContent() && getNextPage() == 0) {
5144 return mCustomContentDescription;
5145 }
5146 return String.format(getContext().getString(R.string.workspace_scroll_format),
5147 page + 1 - delta, getChildCount() - delta);
5148 }
5149
5150 public void getLocationInDragLayer(int[] loc) {
5151 mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
5152 }
5153
5154 /**
5155 * Used as a workaround to ensure that the AppWidgetService receives the
5156 * PACKAGE_ADDED broadcast before updating widgets.
5157 */
5158 private class DeferredWidgetRefresh implements Runnable {
5159 private final ArrayList<LauncherAppWidgetInfo> mInfos;
5160 private final LauncherAppWidgetHost mHost;
5161 private final Handler mHandler;
5162
5163 private boolean mRefreshPending;
5164
5165 public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
5166 LauncherAppWidgetHost host) {
5167 mInfos = infos;
5168 mHost = host;
5169 mHandler = new Handler();
5170 mRefreshPending = true;
5171
5172 mHost.addProviderChangeListener(this);
5173 // Force refresh after 10 seconds, if we don't get the provider changed event.
5174 // This could happen when the provider is no longer available in the app.
5175 mHandler.postDelayed(this, 10000);
5176 }
5177
5178 @Override
5179 public void run() {
5180 mHost.removeProviderChangeListener(this);
5181 mHandler.removeCallbacks(this);
5182
5183 if (!mRefreshPending) {
5184 return;
5185 }
5186
5187 mRefreshPending = false;
5188
5189 for (LauncherAppWidgetInfo info : mInfos) {
5190 if (info.hostView instanceof PendingAppWidgetHostView) {
5191 PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
5192 mLauncher.removeAppWidget(info);
5193
5194 CellLayout cl = (CellLayout) view.getParent().getParent();
5195 // Remove the current widget
5196 cl.removeView(view);
5197 mLauncher.bindAppWidget(info);
5198 }
5199 }
5200 }
5201 }
5202 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.animation.LayoutTransition;
24 import android.animation.ObjectAnimator;
25 import android.animation.PropertyValuesHolder;
26 import android.animation.TimeInterpolator;
27 import android.animation.ValueAnimator;
28 import android.animation.ValueAnimator.AnimatorUpdateListener;
29 import android.app.WallpaperManager;
30 import android.appwidget.AppWidgetHostView;
31 import android.appwidget.AppWidgetProviderInfo;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.SharedPreferences;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ResolveInfo;
38 import android.content.res.Resources;
39 import android.content.res.TypedArray;
40 import android.graphics.Bitmap;
41 import android.graphics.Canvas;
42 import android.graphics.Matrix;
43 import android.graphics.Paint;
44 import android.graphics.Point;
45 import android.graphics.PointF;
46 import android.graphics.Rect;
47 import android.graphics.Region.Op;
48 import android.graphics.drawable.Drawable;
49 import android.net.Uri;
50 import android.os.AsyncTask;
51 import android.os.Handler;
52 import android.os.IBinder;
53 import android.os.Parcelable;
54 import android.support.v4.view.ViewCompat;
55 import android.util.AttributeSet;
56 import android.util.Log;
57 import android.util.SparseArray;
58 import android.view.Choreographer;
59 import android.view.Display;
60 import android.view.MotionEvent;
61 import android.view.View;
62 import android.view.ViewGroup;
63 import android.view.accessibility.AccessibilityManager;
64 import android.view.animation.DecelerateInterpolator;
65 import android.view.animation.Interpolator;
66 import android.widget.TextView;
67
68 import com.android.launcher3.FolderIcon.FolderRingAnimator;
69 import com.android.launcher3.Launcher.CustomContentCallbacks;
70 import com.android.launcher3.LauncherSettings.Favorites;
71 import com.android.launcher3.compat.PackageInstallerCompat;
72 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
73 import com.android.launcher3.compat.UserHandleCompat;
74
75 import java.util.ArrayList;
76 import java.util.HashMap;
77 import java.util.HashSet;
78 import java.util.Iterator;
79 import java.util.Map;
80 import java.util.Set;
81 import java.util.concurrent.atomic.AtomicInteger;
82
83 /**
84 * The workspace is a wide area with a wallpaper and a finite number of pages.
85 * Each page contains a number of icons, folders or widgets the user can
86 * interact with. A workspace is meant to be used with a fixed width only.
87 */
88 public class Workspace extends SmoothPagedView
89 implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
90 DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
91 Insettable {
92 private static final String TAG = "Launcher.Workspace";
93
94 // Y rotation to apply to the workspace screens
95 private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
96
97 private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
98 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
99 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
100
101 protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
102 protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
103
104 private static final int BACKGROUND_FADE_OUT_DURATION = 350;
105 private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
106 private static final int FLING_THRESHOLD_VELOCITY = 500;
107
108 private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
109
110 static final boolean MAP_NO_RECURSE = false;
111 static final boolean MAP_RECURSE = true;
112
113 // These animators are used to fade the children's outlines
114 private ObjectAnimator mChildrenOutlineFadeInAnimation;
115 private ObjectAnimator mChildrenOutlineFadeOutAnimation;
116 private float mChildrenOutlineAlpha = 0;
117
118 // These properties refer to the background protection gradient used for AllApps and Customize
119 private ValueAnimator mBackgroundFadeInAnimation;
120 private ValueAnimator mBackgroundFadeOutAnimation;
121
122 private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
123 private long mTouchDownTime = -1;
124 private long mCustomContentShowTime = -1;
125
126 private LayoutTransition mLayoutTransition;
127 private final WallpaperManager mWallpaperManager;
128 private IBinder mWindowToken;
129
130 private int mOriginalDefaultPage;
131 private int mDefaultPage;
132
133 private ShortcutAndWidgetContainer mDragSourceInternal;
134 private static boolean sAccessibilityEnabled;
135
136 // The screen id used for the empty screen always present to the right.
137 final static long EXTRA_EMPTY_SCREEN_ID = -201;
138 private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
139
140 private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
141 private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
142
143 private Runnable mRemoveEmptyScreenRunnable;
144 private boolean mDeferRemoveExtraEmptyScreen = false;
145
146 /**
147 * CellInfo for the cell that is currently being dragged
148 */
149 private CellLayout.CellInfo mDragInfo;
150
151 /**
152 * Target drop area calculated during last acceptDrop call.
153 */
154 private int[] mTargetCell = new int[2];
155 private int mDragOverX = -1;
156 private int mDragOverY = -1;
157
158 static Rect mLandscapeCellLayoutMetrics = null;
159 static Rect mPortraitCellLayoutMetrics = null;
160
161 CustomContentCallbacks mCustomContentCallbacks;
162 boolean mCustomContentShowing;
163 private float mLastCustomContentScrollProgress = -1f;
164 private String mCustomContentDescription = "";
165
166 /**
167 * The CellLayout that is currently being dragged over
168 */
169 private CellLayout mDragTargetLayout = null;
170 /**
171 * The CellLayout that we will show as glowing
172 */
173 private CellLayout mDragOverlappingLayout = null;
174
175 /**
176 * The CellLayout which will be dropped to
177 */
178 private CellLayout mDropToLayout = null;
179
180 private Launcher mLauncher;
181 private IconCache mIconCache;
182 private DragController mDragController;
183
184 // These are temporary variables to prevent having to allocate a new object just to
185 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
186 private int[] mTempCell = new int[2];
187 private int[] mTempPt = new int[2];
188 private int[] mTempEstimate = new int[2];
189 private float[] mDragViewVisualCenter = new float[2];
190 private float[] mTempCellLayoutCenterCoordinates = new float[2];
191 private Matrix mTempInverseMatrix = new Matrix();
192
193 private SpringLoadedDragController mSpringLoadedDragController;
194 private float mSpringLoadedShrinkFactor;
195 private float mOverviewModeShrinkFactor;
196
197 // State variable that indicates whether the pages are small (ie when you're
198 // in all apps or customize mode)
199
200 enum State { NORMAL, NORMAL_HIDDEN, SPRING_LOADED, OVERVIEW, OVERVIEW_HIDDEN};
201 private State mState = State.NORMAL;
202 private boolean mIsSwitchingState = false;
203
204 boolean mAnimatingViewIntoPlace = false;
205 boolean mIsDragOccuring = false;
206 boolean mChildrenLayersEnabled = true;
207
208 private boolean mStripScreensOnPageStopMoving = false;
209
210 /** Is the user is dragging an item near the edge of a page? */
211 private boolean mInScrollArea = false;
212
213 private HolographicOutlineHelper mOutlineHelper;
214 private Bitmap mDragOutline = null;
215 private static final Rect sTempRect = new Rect();
216 private final int[] mTempXY = new int[2];
217 private int[] mTempVisiblePagesRange = new int[2];
218 private boolean mOverscrollEffectSet;
219 public static final int DRAG_BITMAP_PADDING = 2;
220 private boolean mWorkspaceFadeInAdjacentScreens;
221
222 WallpaperOffsetInterpolator mWallpaperOffset;
223 private boolean mWallpaperIsLiveWallpaper;
224 private int mNumPagesForWallpaperParallax;
225 private float mLastSetWallpaperOffsetSteps = 0;
226
227 private Runnable mDelayedResizeRunnable;
228 private Runnable mDelayedSnapToPageRunnable;
229 private Point mDisplaySize = new Point();
230 private int mCameraDistance;
231
232 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
233 private static final int FOLDER_CREATION_TIMEOUT = 0;
234 public static final int REORDER_TIMEOUT = 350;
235 private final Alarm mFolderCreationAlarm = new Alarm();
236 private final Alarm mReorderAlarm = new Alarm();
237 private FolderRingAnimator mDragFolderRingAnimator = null;
238 private FolderIcon mDragOverFolderIcon = null;
239 private boolean mCreateUserFolderOnDrop = false;
240 private boolean mAddToExistingFolderOnDrop = false;
241 private DropTarget.DragEnforcer mDragEnforcer;
242 private float mMaxDistanceForFolderCreation;
243
244 private final Canvas mCanvas = new Canvas();
245
246 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
247 private float mXDown;
248 private float mYDown;
249 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
250 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
251 final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
252
253 // Relating to the animation of items being dropped externally
254 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
255 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
256 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
257 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
258 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
259
260 // Related to dragging, folder creation and reordering
261 private static final int DRAG_MODE_NONE = 0;
262 private static final int DRAG_MODE_CREATE_FOLDER = 1;
263 private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
264 private static final int DRAG_MODE_REORDER = 3;
265 private int mDragMode = DRAG_MODE_NONE;
266 private int mLastReorderX = -1;
267 private int mLastReorderY = -1;
268
269 private SparseArray<Parcelable> mSavedStates;
270 private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
271
272 // These variables are used for storing the initial and final values during workspace animations
273 private int mSavedScrollX;
274 private float mSavedRotationY;
275 private float mSavedTranslationX;
276
277 private float mCurrentScale;
278 private float mNewScale;
279 private float[] mOldBackgroundAlphas;
280 private float[] mOldAlphas;
281 private float[] mNewBackgroundAlphas;
282 private float[] mNewAlphas;
283 private int mLastChildCount = -1;
284 private float mTransitionProgress;
285
286 float mOverScrollEffect = 0f;
287
288 private Runnable mDeferredAction;
289 private boolean mDeferDropAfterUninstall;
290 private boolean mUninstallSuccessful;
291
292 private final Runnable mBindPages = new Runnable() {
293 @Override
294 public void run() {
295 mLauncher.getModel().bindRemainingSynchronousPages();
296 }
297 };
298
299 /**
300 * Used to inflate the Workspace from XML.
301 *
302 * @param context The application's context.
303 * @param attrs The attributes set containing the Workspace's customization values.
304 */
305 public Workspace(Context context, AttributeSet attrs) {
306 this(context, attrs, 0);
307 }
308
309 /**
310 * Used to inflate the Workspace from XML.
311 *
312 * @param context The application's context.
313 * @param attrs The attributes set containing the Workspace's customization values.
314 * @param defStyle Unused.
315 */
316 public Workspace(Context context, AttributeSet attrs, int defStyle) {
317 super(context, attrs, defStyle);
318 mContentIsRefreshable = false;
319
320 mOutlineHelper = HolographicOutlineHelper.obtain(context);
321
322 mDragEnforcer = new DropTarget.DragEnforcer(context);
323 // With workspace, data is available straight from the get-go
324 setDataIsReady();
325
326 mLauncher = (Launcher) context;
327 final Resources res = getResources();
328 mWorkspaceFadeInAdjacentScreens = LauncherAppState.getInstance().getDynamicGrid().
329 getDeviceProfile().shouldFadeAdjacentWorkspaceScreens();
330 mFadeInAdjacentScreens = false;
331 mWallpaperManager = WallpaperManager.getInstance(context);
332
333 LauncherAppState app = LauncherAppState.getInstance();
334 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
335 TypedArray a = context.obtainStyledAttributes(attrs,
336 R.styleable.Workspace, defStyle, 0);
337 mSpringLoadedShrinkFactor =
338 res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f;
339 mOverviewModeShrinkFactor = grid.getOverviewModeScale();
340 mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
341 mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
342 a.recycle();
343
344 setOnHierarchyChangeListener(this);
345 setHapticFeedbackEnabled(false);
346
347 initWorkspace();
348
349 // Disable multitouch across the workspace/all apps/customize tray
350 setMotionEventSplittingEnabled(true);
351 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
352 }
353
354 @Override
355 public void setInsets(Rect insets) {
356 mInsets.set(insets);
357
358 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
359 if (customScreen != null) {
360 View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
361 if (customContent instanceof Insettable) {
362 ((Insettable) customContent).setInsets(mInsets);
363 }
364 }
365 }
366
367 // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
368 // dimension if unsuccessful
369 public int[] estimateItemSize(int hSpan, int vSpan,
370 ItemInfo itemInfo, boolean springLoaded) {
371 int[] size = new int[2];
372 if (getChildCount() > 0) {
373 // Use the first non-custom page to estimate the child position
374 CellLayout cl = (CellLayout) getChildAt(numCustomPages());
375 Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
376 size[0] = r.width();
377 size[1] = r.height();
378 if (springLoaded) {
379 size[0] *= mSpringLoadedShrinkFactor;
380 size[1] *= mSpringLoadedShrinkFactor;
381 }
382 return size;
383 } else {
384 size[0] = Integer.MAX_VALUE;
385 size[1] = Integer.MAX_VALUE;
386 return size;
387 }
388 }
389
390 public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
391 int hCell, int vCell, int hSpan, int vSpan) {
392 Rect r = new Rect();
393 cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
394 return r;
395 }
396
397 public void onDragStart(final DragSource source, Object info, int dragAction) {
398 mIsDragOccuring = true;
399 updateChildrenLayersEnabled(false);
400 mLauncher.lockScreenOrientation();
401 mLauncher.onInteractionBegin();
402 setChildrenBackgroundAlphaMultipliers(1f);
403 // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
404 InstallShortcutReceiver.enableInstallQueue();
405 UninstallShortcutReceiver.enableUninstallQueue();
406 post(new Runnable() {
407 @Override
408 public void run() {
409 if (mIsDragOccuring) {
410 mDeferRemoveExtraEmptyScreen = false;
411 addExtraEmptyScreenOnDrag();
412 }
413 }
414 });
415 }
416
417
418 public void deferRemoveExtraEmptyScreen() {
419 mDeferRemoveExtraEmptyScreen = true;
420 }
421
422 public void onDragEnd() {
423 if (!mDeferRemoveExtraEmptyScreen) {
424 removeExtraEmptyScreen(true, mDragSourceInternal != null);
425 }
426
427 mIsDragOccuring = false;
428 updateChildrenLayersEnabled(false);
429 mLauncher.unlockScreenOrientation(false);
430
431 // Re-enable any Un/InstallShortcutReceiver and now process any queued items
432 InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
433 UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
434
435 mDragSourceInternal = null;
436 mLauncher.onInteractionEnd();
437 }
438
439 /**
440 * Initializes various states for this workspace.
441 */
442 protected void initWorkspace() {
443 mCurrentPage = mDefaultPage;
444 Launcher.setScreen(mCurrentPage);
445 LauncherAppState app = LauncherAppState.getInstance();
446 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
447 mIconCache = app.getIconCache();
448 setWillNotDraw(false);
449 setClipChildren(false);
450 setClipToPadding(false);
451 setChildrenDrawnWithCacheEnabled(true);
452
453 setMinScale(mOverviewModeShrinkFactor);
454 setupLayoutTransition();
455
456 mWallpaperOffset = new WallpaperOffsetInterpolator();
457 Display display = mLauncher.getWindowManager().getDefaultDisplay();
458 display.getSize(mDisplaySize);
459
460 mMaxDistanceForFolderCreation = (0.55f * grid.iconSizePx);
461 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
462
463 // Set the wallpaper dimensions when Launcher starts up
464 setWallpaperDimension();
465 }
466
467 private void setupLayoutTransition() {
468 // We want to show layout transitions when pages are deleted, to close the gap.
469 mLayoutTransition = new LayoutTransition();
470 mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
471 mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
472 mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
473 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
474 setLayoutTransition(mLayoutTransition);
475 }
476
477 void enableLayoutTransitions() {
478 setLayoutTransition(mLayoutTransition);
479 }
480 void disableLayoutTransitions() {
481 setLayoutTransition(null);
482 }
483
484 @Override
485 protected int getScrollMode() {
486 return SmoothPagedView.X_LARGE_MODE;
487 }
488
489 @Override
490 public void onChildViewAdded(View parent, View child) {
491 if (!(child instanceof CellLayout)) {
492 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
493 }
494 CellLayout cl = ((CellLayout) child);
495 cl.setOnInterceptTouchListener(this);
496 cl.setClickable(true);
497 cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
498 super.onChildViewAdded(parent, child);
499 }
500
501 protected boolean shouldDrawChild(View child) {
502 final CellLayout cl = (CellLayout) child;
503 return super.shouldDrawChild(child) &&
504 (mIsSwitchingState ||
505 cl.getShortcutsAndWidgets().getAlpha() > 0 ||
506 cl.getBackgroundAlpha() > 0);
507 }
508
509 /**
510 * @return The open folder on the current screen, or null if there is none
511 */
512 Folder getOpenFolder() {
513 DragLayer dragLayer = mLauncher.getDragLayer();
514 int count = dragLayer.getChildCount();
515 for (int i = 0; i < count; i++) {
516 View child = dragLayer.getChildAt(i);
517 if (child instanceof Folder) {
518 Folder folder = (Folder) child;
519 if (folder.getInfo().opened)
520 return folder;
521 }
522 }
523 return null;
524 }
525
526 boolean isTouchActive() {
527 return mTouchState != TOUCH_STATE_REST;
528 }
529
530 public void removeAllWorkspaceScreens() {
531 // Disable all layout transitions before removing all pages to ensure that we don't get the
532 // transition animations competing with us changing the scroll when we add pages or the
533 // custom content screen
534 disableLayoutTransitions();
535
536 // Since we increment the current page when we call addCustomContentPage via bindScreens
537 // (and other places), we need to adjust the current page back when we clear the pages
538 if (hasCustomContent()) {
539 removeCustomContentPage();
540 }
541
542 // Remove the pages and clear the screen models
543 removeAllViews();
544 mScreenOrder.clear();
545 mWorkspaceScreens.clear();
546
547 // Re-enable the layout transitions
548 enableLayoutTransitions();
549 }
550
551 public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
552 // Find the index to insert this view into. If the empty screen exists, then
553 // insert it before that.
554 int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
555 if (insertIndex < 0) {
556 insertIndex = mScreenOrder.size();
557 }
558 return insertNewWorkspaceScreen(screenId, insertIndex);
559 }
560
561 public long insertNewWorkspaceScreen(long screenId) {
562 return insertNewWorkspaceScreen(screenId, getChildCount());
563 }
564
565 public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
566 // Log to disk
567 Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
568 " at index: " + insertIndex, true);
569
570 if (mWorkspaceScreens.containsKey(screenId)) {
571 throw new RuntimeException("Screen id " + screenId + " already exists!");
572 }
573
574 CellLayout newScreen = (CellLayout)
575 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
576
577 newScreen.setOnLongClickListener(mLongClickListener);
578 newScreen.setOnClickListener(mLauncher);
579 newScreen.setSoundEffectsEnabled(false);
580 mWorkspaceScreens.put(screenId, newScreen);
581 mScreenOrder.add(insertIndex, screenId);
582 addView(newScreen, insertIndex);
583 return screenId;
584 }
585
586 public void createCustomContentContainer() {
587 CellLayout customScreen = (CellLayout)
588 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
589 customScreen.disableBackground();
590 customScreen.disableDragTarget();
591
592 mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
593 mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
594
595 // We want no padding on the custom content
596 customScreen.setPadding(0, 0, 0, 0);
597
598 addFullScreenPage(customScreen);
599
600 // Ensure that the current page and default page are maintained.
601 mDefaultPage = mOriginalDefaultPage + 1;
602
603 // Update the custom content hint
604 if (mRestorePage != INVALID_RESTORE_PAGE) {
605 mRestorePage = mRestorePage + 1;
606 } else {
607 setCurrentPage(getCurrentPage() + 1);
608 }
609 }
610
611 public void removeCustomContentPage() {
612 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
613 if (customScreen == null) {
614 throw new RuntimeException("Expected custom content screen to exist");
615 }
616
617 mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
618 mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
619 removeView(customScreen);
620
621 if (mCustomContentCallbacks != null) {
622 mCustomContentCallbacks.onScrollProgressChanged(0);
623 mCustomContentCallbacks.onHide();
624 }
625
626 mCustomContentCallbacks = null;
627
628 // Ensure that the current page and default page are maintained.
629 mDefaultPage = mOriginalDefaultPage - 1;
630
631 // Update the custom content hint
632 if (mRestorePage != INVALID_RESTORE_PAGE) {
633 mRestorePage = mRestorePage - 1;
634 } else {
635 setCurrentPage(getCurrentPage() - 1);
636 }
637 }
638
639 public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
640 String description) {
641 if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
642 throw new RuntimeException("Expected custom content screen to exist");
643 }
644
645 // Add the custom content to the full screen custom page
646 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
647 int spanX = customScreen.getCountX();
648 int spanY = customScreen.getCountY();
649 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
650 lp.canReorder = false;
651 lp.isFullscreen = true;
652 if (customContent instanceof Insettable) {
653 ((Insettable)customContent).setInsets(mInsets);
654 }
655
656 // Verify that the child is removed from any existing parent.
657 if (customContent.getParent() instanceof ViewGroup) {
658 ViewGroup parent = (ViewGroup) customContent.getParent();
659 parent.removeView(customContent);
660 }
661 customScreen.removeAllViews();
662 customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
663 mCustomContentDescription = description;
664
665 mCustomContentCallbacks = callbacks;
666 }
667
668 public void addExtraEmptyScreenOnDrag() {
669 // Log to disk
670 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
671
672 boolean lastChildOnScreen = false;
673 boolean childOnFinalScreen = false;
674
675 // Cancel any pending removal of empty screen
676 mRemoveEmptyScreenRunnable = null;
677
678 if (mDragSourceInternal != null) {
679 if (mDragSourceInternal.getChildCount() == 1) {
680 lastChildOnScreen = true;
681 }
682 CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
683 if (indexOfChild(cl) == getChildCount() - 1) {
684 childOnFinalScreen = true;
685 }
686 }
687
688 // If this is the last item on the final screen
689 if (lastChildOnScreen && childOnFinalScreen) {
690 return;
691 }
692 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
693 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
694 }
695 }
696
697 public boolean addExtraEmptyScreen() {
698 // Log to disk
699 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
700
701 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
702 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
703 return true;
704 }
705 return false;
706 }
707
708 private void convertFinalScreenToEmptyScreenIfNecessary() {
709 // Log to disk
710 Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
711
712 if (mLauncher.isWorkspaceLoading()) {
713 // Invalid and dangerous operation if workspace is loading
714 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
715 return;
716 }
717
718 if (hasExtraEmptyScreen() || mScreenOrder.size() == 0) return;
719 long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
720
721 if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) return;
722 CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
723
724 // If the final screen is empty, convert it to the extra empty screen
725 if (finalScreen.getShortcutsAndWidgets().getChildCount() == 0 &&
726 !finalScreen.isDropPending()) {
727 mWorkspaceScreens.remove(finalScreenId);
728 mScreenOrder.remove(finalScreenId);
729
730 // if this is the last non-custom content screen, convert it to the empty screen
731 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
732 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
733
734 // Update the model if we have changed any screens
735 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
736 Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
737 }
738 }
739
740 public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
741 removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
742 }
743
744 public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
745 final int delay, final boolean stripEmptyScreens) {
746 // Log to disk
747 Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
748 if (mLauncher.isWorkspaceLoading()) {
749 // Don't strip empty screens if the workspace is still loading
750 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
751 return;
752 }
753
754 if (delay > 0) {
755 postDelayed(new Runnable() {
756 @Override
757 public void run() {
758 removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
759 }
760 }, delay);
761 return;
762 }
763
764 convertFinalScreenToEmptyScreenIfNecessary();
765 if (hasExtraEmptyScreen()) {
766 int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
767 if (getNextPage() == emptyIndex) {
768 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
769 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION,
770 onComplete, stripEmptyScreens);
771 } else {
772 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION,
773 onComplete, stripEmptyScreens);
774 }
775 return;
776 } else if (stripEmptyScreens) {
777 // If we're not going to strip the empty screens after removing
778 // the extra empty screen, do it right away.
779 stripEmptyScreens();
780 }
781
782 if (onComplete != null) {
783 onComplete.run();
784 }
785 }
786
787 private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
788 final boolean stripEmptyScreens) {
789 // Log to disk
790 // XXX: Do we need to update LM workspace screens below?
791 Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
792 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
793 PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
794
795 final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
796
797 mRemoveEmptyScreenRunnable = new Runnable() {
798 @Override
799 public void run() {
800 if (hasExtraEmptyScreen()) {
801 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
802 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
803 removeView(cl);
804 if (stripEmptyScreens) {
805 stripEmptyScreens();
806 }
807 }
808 }
809 };
810
811 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
812 oa.setDuration(duration);
813 oa.setStartDelay(delay);
814 oa.addListener(new AnimatorListenerAdapter() {
815 @Override
816 public void onAnimationEnd(Animator animation) {
817 if (mRemoveEmptyScreenRunnable != null) {
818 mRemoveEmptyScreenRunnable.run();
819 }
820 if (onComplete != null) {
821 onComplete.run();
822 }
823 }
824 });
825 oa.start();
826 }
827
828 public boolean hasExtraEmptyScreen() {
829 int nScreens = getChildCount();
830 nScreens = nScreens - numCustomPages();
831 return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
832 }
833
834 public long commitExtraEmptyScreen() {
835 // Log to disk
836 Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
837 if (mLauncher.isWorkspaceLoading()) {
838 // Invalid and dangerous operation if workspace is loading
839 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
840 return -1;
841 }
842
843 int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
844 CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
845 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
846 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
847
848 long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
849 mWorkspaceScreens.put(newId, cl);
850 mScreenOrder.add(newId);
851
852 // Update the page indicator marker
853 if (getPageIndicator() != null) {
854 getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
855 }
856
857 // Update the model for the new screen
858 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
859
860 return newId;
861 }
862
863 public CellLayout getScreenWithId(long screenId) {
864 CellLayout layout = mWorkspaceScreens.get(screenId);
865 return layout;
866 }
867
868 public long getIdForScreen(CellLayout layout) {
869 Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
870 while (iter.hasNext()) {
871 long id = iter.next();
872 if (mWorkspaceScreens.get(id) == layout) {
873 return id;
874 }
875 }
876 return -1;
877 }
878
879 public int getPageIndexForScreenId(long screenId) {
880 return indexOfChild(mWorkspaceScreens.get(screenId));
881 }
882
883 public long getScreenIdForPageIndex(int index) {
884 if (0 <= index && index < mScreenOrder.size()) {
885 return mScreenOrder.get(index);
886 }
887 return -1;
888 }
889
890 ArrayList<Long> getScreenOrder() {
891 return mScreenOrder;
892 }
893
894 public void stripEmptyScreens() {
895 // Log to disk
896 Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
897
898 if (mLauncher.isWorkspaceLoading()) {
899 // Don't strip empty screens if the workspace is still loading.
900 // This is dangerous and can result in data loss.
901 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
902 return;
903 }
904
905 if (isPageMoving()) {
906 mStripScreensOnPageStopMoving = true;
907 return;
908 }
909
910 int currentPage = getNextPage();
911 ArrayList<Long> removeScreens = new ArrayList<Long>();
912 for (Long id: mWorkspaceScreens.keySet()) {
913 CellLayout cl = mWorkspaceScreens.get(id);
914 if (id >= 0 && cl.getShortcutsAndWidgets().getChildCount() == 0) {
915 removeScreens.add(id);
916 }
917 }
918
919 // We enforce at least one page to add new items to. In the case that we remove the last
920 // such screen, we convert the last screen to the empty screen
921 int minScreens = 1 + numCustomPages();
922
923 int pageShift = 0;
924 for (Long id: removeScreens) {
925 Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
926 CellLayout cl = mWorkspaceScreens.get(id);
927 mWorkspaceScreens.remove(id);
928 mScreenOrder.remove(id);
929
930 if (getChildCount() > minScreens) {
931 if (indexOfChild(cl) < currentPage) {
932 pageShift++;
933 }
934 removeView(cl);
935 } else {
936 // if this is the last non-custom content screen, convert it to the empty screen
937 mRemoveEmptyScreenRunnable = null;
938 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
939 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
940 }
941 }
942
943 if (!removeScreens.isEmpty()) {
944 // Update the model if we have changed any screens
945 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
946 }
947
948 if (pageShift >= 0) {
949 setCurrentPage(currentPage - pageShift);
950 }
951 }
952
953 // See implementation for parameter definition.
954 void addInScreen(View child, long container, long screenId,
955 int x, int y, int spanX, int spanY) {
956 addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
957 }
958
959 // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
960 // See implementation for parameter definition.
961 void addInScreenFromBind(View child, long container, long screenId, int x, int y,
962 int spanX, int spanY) {
963 addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
964 }
965
966 // See implementation for parameter definition.
967 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
968 boolean insert) {
969 addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
970 }
971
972 /**
973 * Adds the specified child in the specified screen. The position and dimension of
974 * the child are defined by x, y, spanX and spanY.
975 *
976 * @param child The child to add in one of the workspace's screens.
977 * @param screenId The screen in which to add the child.
978 * @param x The X position of the child in the screen's grid.
979 * @param y The Y position of the child in the screen's grid.
980 * @param spanX The number of cells spanned horizontally by the child.
981 * @param spanY The number of cells spanned vertically by the child.
982 * @param insert When true, the child is inserted at the beginning of the children list.
983 * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
984 * the x and y position in which to place hotseat items. Otherwise
985 * we use the x and y position to compute the rank.
986 */
987 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
988 boolean insert, boolean computeXYFromRank) {
989 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
990 if (getScreenWithId(screenId) == null) {
991 Log.e(TAG, "Skipping child, screenId " + screenId + " not found");
992 // DEBUGGING - Print out the stack trace to see where we are adding from
993 new Throwable().printStackTrace();
994 return;
995 }
996 }
997 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
998 // This should never happen
999 throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
1000 }
1001
1002 final CellLayout layout;
1003 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1004 layout = mLauncher.getHotseat().getLayout();
1005 child.setOnKeyListener(new HotseatIconKeyEventListener());
1006
1007 // Hide folder title in the hotseat
1008 if (child instanceof FolderIcon) {
1009 ((FolderIcon) child).setTextVisible(false);
1010 }
1011
1012 if (computeXYFromRank) {
1013 x = mLauncher.getHotseat().getCellXFromOrder((int) screenId);
1014 y = mLauncher.getHotseat().getCellYFromOrder((int) screenId);
1015 } else {
1016 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
1017 }
1018 } else {
1019 // Show folder title if not in the hotseat
1020 if (child instanceof FolderIcon) {
1021 ((FolderIcon) child).setTextVisible(true);
1022 }
1023 layout = getScreenWithId(screenId);
1024 child.setOnKeyListener(new IconKeyEventListener());
1025 }
1026
1027 ViewGroup.LayoutParams genericLp = child.getLayoutParams();
1028 CellLayout.LayoutParams lp;
1029 if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
1030 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
1031 } else {
1032 lp = (CellLayout.LayoutParams) genericLp;
1033 lp.cellX = x;
1034 lp.cellY = y;
1035 lp.cellHSpan = spanX;
1036 lp.cellVSpan = spanY;
1037 }
1038
1039 if (spanX < 0 && spanY < 0) {
1040 lp.isLockedToGrid = false;
1041 }
1042
1043 // Get the canonical child id to uniquely represent this view in this screen
1044 ItemInfo info = (ItemInfo) child.getTag();
1045 int childId = mLauncher.getViewIdForItem(info);
1046
1047 boolean markCellsAsOccupied = !(child instanceof Folder);
1048 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
1049 // TODO: This branch occurs when the workspace is adding views
1050 // outside of the defined grid
1051 // maybe we should be deleting these items from the LauncherModel?
1052 Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to Cel🔵
1053 }
1054
1055 if (!(child instanceof Folder)) {
1056 child.setHapticFeedbackEnabled(false);
1057 child.setOnLongClickListener(mLongClickListener);
1058 }
1059 if (child instanceof DropTarget) {
1060 mDragController.addDropTarget((DropTarget) child);
1061 }
1062 }
1063
1064 /**
1065 * Called directly from a CellLayout (not by the framework), after we've been added as a
1066 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
1067 * that it should intercept touch events, which is not something that is normally supported.
1068 */
1069 @Override
1070 public boolean onTouch(View v, MotionEvent event) {
1071 return (workspaceInModalState() || !isFinishedSwitchingState())
1072 || (!workspaceInModalState() && indexOfChild(v) != mCurrentPage);
1073 }
1074
1075 public boolean isSwitchingState() {
1076 return mIsSwitchingState;
1077 }
1078
1079 /** This differs from isSwitchingState in that we take into account how far the transition
1080 * has completed. */
1081 public boolean isFinishedSwitchingState() {
1082 return !mIsSwitchingState || (mTransitionProgress > 0.5f);
1083 }
1084
1085 protected void onWindowVisibilityChanged (int visibility) {
1086 mLauncher.onWindowVisibilityChanged(visibility);
1087 }
1088
1089 @Override
1090 public boolean dispatchUnhandledMove(View focused, int direction) {
1091 if (workspaceInModalState() || !isFinishedSwitchingState()) {
1092 // when the home screens are shrunken, shouldn't allow side-scrolling
1093 return false;
1094 }
1095 return super.dispatchUnhandledMove(focused, direction);
1096 }
1097
1098 @Override
1099 public boolean onInterceptTouchEvent(MotionEvent ev) {
1100 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1101 case MotionEvent.ACTION_DOWN:
1102 mXDown = ev.getX();
1103 mYDown = ev.getY();
1104 mTouchDownTime = System.currentTimeMillis();
1105 break;
1106 case MotionEvent.ACTION_POINTER_UP:
1107 case MotionEvent.ACTION_UP:
1108 if (mTouchState == TOUCH_STATE_REST) {
1109 final CellLayout currentPage = (CellLayout) getChildAt(mCurrentPage);
1110 if (currentPage != null && !currentPage.lastDownOnOccupiedCell()) {
1111 onWallpaperTap(ev);
1112 }
1113 }
1114 }
1115 return super.onInterceptTouchEvent(ev);
1116 }
1117
1118 @Override
1119 public boolean onGenericMotionEvent(MotionEvent event) {
1120 // Ignore pointer scroll events if the custom content doesn't allow scrolling.
1121 if ((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID)
1122 && (mCustomContentCallbacks != null)
1123 && !mCustomContentCallbacks.isScrollingAllowed()) {
1124 return false;
1125 }
1126 return super.onGenericMotionEvent(event);
1127 }
1128
1129 protected void reinflateWidgetsIfNecessary() {
1130 final int clCount = getChildCount();
1131 for (int i = 0; i < clCount; i++) {
1132 CellLayout cl = (CellLayout) getChildAt(i);
1133 ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
1134 final int itemCount = swc.getChildCount();
1135 for (int j = 0; j < itemCount; j++) {
1136 View v = swc.getChildAt(j);
1137
1138 if (v != null && v.getTag() instanceof LauncherAppWidgetInfo) {
1139 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
1140 LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView;
1141 if (lahv != null && lahv.isReinflateRequired()) {
1142 mLauncher.removeAppWidget(info);
1143 // Remove the current widget which is inflated with the wrong orientation
1144 cl.removeView(lahv);
1145 mLauncher.bindAppWidget(info);
1146 }
1147 }
1148 }
1149 }
1150 }
1151
1152 @Override
1153 protected void determineScrollingStart(MotionEvent ev) {
1154 if (!isFinishedSwitchingState()) return;
1155
1156 float deltaX = ev.getX() - mXDown;
1157 float absDeltaX = Math.abs(deltaX);
1158 float absDeltaY = Math.abs(ev.getY() - mYDown);
1159
1160 if (Float.compare(absDeltaX, 0f) == 0) return;
1161
1162 float slope = absDeltaY / absDeltaX;
1163 float theta = (float) Math.atan(slope);
1164
1165 if (absDeltaX > mTouchSlop || absDeltaY > mTouchSlop) {
1166 cancelCurrentPageLongPress();
1167 }
1168
1169 boolean passRightSwipesToCustomContent =
1170 (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTENT_GESTURE_DELAY;
1171
1172 boolean swipeInIgnoreDirection = isLayoutRtl() ? deltaX < 0 : deltaX > 0;
1173 boolean onCustomContentScreen =
1174 getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID;
1175 if (swipeInIgnoreDirection && onCustomContentScreen && passRightSwipesToCustomContent) {
1176 // Pass swipes to the right to the custom content page.
1177 return;
1178 }
1179
1180 if (onCustomContentScreen && (mCustomContentCallbacks != null)
1181 && !mCustomContentCallbacks.isScrollingAllowed()) {
1182 // Don't allow workspace scrolling if the current custom content screen doesn't allow
1183 // scrolling.
1184 return;
1185 }
1186
1187 if (theta > MAX_SWIPE_ANGLE) {
1188 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1189 return;
1190 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1191 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1192 // increase the touch slop to make it harder to begin scrolling the workspace. This
1193 // results in vertically scrolling widgets to more easily. The higher the angle, the
1194 // more we increase touch slop.
1195 theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1196 float extraRatio = (float)
1197 Math.sqrt((theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_ANGLE)));
1198 super.determineScrollingStart(ev, 1 + TOUCH_SLOP_DAMPING_FACTOR * extraRatio);
1199 } else {
1200 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1201 super.determineScrollingStart(ev);
1202 }
1203 }
1204
1205 protected void onPageBeginMoving() {
1206 super.onPageBeginMoving();
1207
1208 if (isHardwareAccelerated()) {
1209 updateChildrenLayersEnabled(false);
1210 } else {
1211 if (mNextPage != INVALID_PAGE) {
1212 // we're snapping to a particular screen
1213 enableChildrenCache(mCurrentPage, mNextPage);
1214 } else {
1215 // this is when user is actively dragging a particular screen, they might
1216 // swipe it either left or right (but we won't advance by more than one screen)
1217 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
1218 }
1219 }
1220 }
1221
1222 protected void onPageEndMoving() {
1223 super.onPageEndMoving();
1224
1225 if (isHardwareAccelerated()) {
1226 updateChildrenLayersEnabled(false);
1227 } else {
1228 clearChildrenCache();
1229 }
1230
1231 if (mDragController.isDragging()) {
1232 if (workspaceInModalState()) {
1233 // If we are in springloaded mode, then force an event to check if the current touch
1234 // is under a new page (to scroll to)
1235 mDragController.forceTouchMove();
1236 }
1237 }
1238
1239 if (mDelayedResizeRunnable != null) {
1240 mDelayedResizeRunnable.run();
1241 mDelayedResizeRunnable = null;
1242 }
1243
1244 if (mDelayedSnapToPageRunnable != null) {
1245 mDelayedSnapToPageRunnable.run();
1246 mDelayedSnapToPageRunnable = null;
1247 }
1248 if (mStripScreensOnPageStopMoving) {
1249 stripEmptyScreens();
1250 mStripScreensOnPageStopMoving = false;
1251 }
1252 }
1253
1254 @Override
1255 protected void notifyPageSwitchListener() {
1256 super.notifyPageSwitchListener();
1257 Launcher.setScreen(getNextPage());
1258
1259 if (hasCustomContent() && getNextPage() == 0 && !mCustomContentShowing) {
1260 mCustomContentShowing = true;
1261 if (mCustomContentCallbacks != null) {
1262 mCustomContentCallbacks.onShow(false);
1263 mCustomContentShowTime = System.currentTimeMillis();
1264 mLauncher.updateVoiceButtonProxyVisible(false);
1265 }
1266 } else if (hasCustomContent() && getNextPage() != 0 && mCustomContentShowing) {
1267 mCustomContentShowing = false;
1268 if (mCustomContentCallbacks != null) {
1269 mCustomContentCallbacks.onHide();
1270 mLauncher.resetQSBScroll();
1271 mLauncher.updateVoiceButtonProxyVisible(false);
1272 }
1273 }
1274 }
1275
1276 protected CustomContentCallbacks getCustomContentCallbacks() {
1277 return mCustomContentCallbacks;
1278 }
1279
1280 protected void setWallpaperDimension() {
1281 new AsyncTask<Void, Void, Void>() {
1282 public Void doInBackground(Void ... args) {
1283 String spKey = WallpaperCropActivity.getSharedPreferencesKey();
1284 SharedPreferences sp =
1285 mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
1286 LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
1287 sp, mLauncher.getWindowManager(), mWallpaperManager,
1288 mLauncher.overrideWallpaperDimensions());
1289 return null;
1290 }
1291 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
1292 }
1293
1294 protected void snapToPage(int whichPage, Runnable r) {
1295 snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
1296 }
1297
1298 protected void snapToPage(int whichPage, int duration, Runnable r) {
1299 if (mDelayedSnapToPageRunnable != null) {
1300 mDelayedSnapToPageRunnable.run();
1301 }
1302 mDelayedSnapToPageRunnable = r;
1303 snapToPage(whichPage, duration);
1304 }
1305
1306 public void snapToScreenId(long screenId) {
1307 snapToScreenId(screenId, null);
1308 }
1309
1310 protected void snapToScreenId(long screenId, Runnable r) {
1311 snapToPage(getPageIndexForScreenId(screenId), r);
1312 }
1313
1314 class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
1315 float mFinalOffset = 0.0f;
1316 float mCurrentOffset = 0.5f; // to force an initial update
1317 boolean mWaitingForUpdate;
1318 Choreographer mChoreographer;
1319 Interpolator mInterpolator;
1320 boolean mAnimating;
1321 long mAnimationStartTime;
1322 float mAnimationStartOffset;
1323 private final int ANIMATION_DURATION = 250;
1324 // Don't use all the wallpaper for parallax until you have at least this many pages
1325 private final int MIN_PARALLAX_PAGE_SPAN = 3;
1326 int mNumScreens;
1327
1328 public WallpaperOffsetInterpolator() {
1329 mChoreographer = Choreographer.getInstance();
1330 mInterpolator = new DecelerateInterpolator(1.5f);
1331 }
1332
1333 @Override
1334 public void doFrame(long frameTimeNanos) {
1335 updateOffset(false);
1336 }
1337
1338 private void updateOffset(boolean force) {
1339 if (mWaitingForUpdate || force) {
1340 mWaitingForUpdate = false;
1341 if (computeScrollOffset() && mWindowToken != null) {
1342 try {
1343 mWallpaperManager.setWallpaperOffsets(mWindowToken,
1344 mWallpaperOffset.getCurrX(), 0.5f);
1345 setWallpaperOffsetSteps();
1346 } catch (IllegalArgumentException e) {
1347 Log.e(TAG, "Error updating wallpaper offset: " + e);
1348 }
1349 }
1350 }
1351 }
1352
1353 public boolean computeScrollOffset() {
1354 final float oldOffset = mCurrentOffset;
1355 if (mAnimating) {
1356 long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
1357 float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
1358 float t1 = mInterpolator.getInterpolation(t0);
1359 mCurrentOffset = mAnimationStartOffset +
1360 (mFinalOffset - mAnimationStartOffset) * t1;
1361 mAnimating = durationSinceAnimation < ANIMATION_DURATION;
1362 } else {
1363 mCurrentOffset = mFinalOffset;
1364 }
1365
1366 if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
1367 scheduleUpdate();
1368 }
1369 if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
1370 return true;
1371 }
1372 return false;
1373 }
1374
1375 private float wallpaperOffsetForCurrentScroll() {
1376 if (getChildCount() <= 1) {
1377 return 0;
1378 }
1379
1380 // Exclude the leftmost page
1381 int emptyExtraPages = numEmptyScreensToIgnore();
1382 int firstIndex = numCustomPages();
1383 // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
1384 int lastIndex = getChildCount() - 1 - emptyExtraPages;
1385 if (isLayoutRtl()) {
1386 int temp = firstIndex;
1387 firstIndex = lastIndex;
1388 lastIndex = temp;
1389 }
1390
1391 int firstPageScrollX = getScrollForPage(firstIndex);
1392 int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
1393 if (scrollRange == 0) {
1394 return 0;
1395 } else {
1396 // TODO: do different behavior if it's a live wallpaper?
1397 // Sometimes the left parameter of the pages is animated during a layout transition;
1398 // this parameter offsets it to keep the wallpaper from animating as well
1399 int adjustedScroll =
1400 getScrollX() - firstPageScrollX - getLayoutTransitionOffsetForPage(0);
1401 float offset = Math.min(1, adjustedScroll / (float) scrollRange);
1402 offset = Math.max(0, offset);
1403 // Don't use up all the wallpaper parallax until you have at least
1404 // MIN_PARALLAX_PAGE_SPAN pages
1405 int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
1406 int parallaxPageSpan;
1407 if (mWallpaperIsLiveWallpaper) {
1408 parallaxPageSpan = numScrollingPages - 1;
1409 } else {
1410 parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
1411 }
1412 mNumPagesForWallpaperParallax = parallaxPageSpan;
1413
1414 // On RTL devices, push the wallpaper offset to the right if we don't have enough
1415 // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
1416 int padding = isLayoutRtl() ? parallaxPageSpan - numScrollingPages + 1 : 0;
1417 return offset * (padding + numScrollingPages - 1) / parallaxPageSpan;
1418 }
1419 }
1420
1421 private int numEmptyScreensToIgnore() {
1422 int numScrollingPages = getChildCount() - numCustomPages();
1423 if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && hasExtraEmptyScreen()) {
1424 return 1;
1425 } else {
1426 return 0;
1427 }
1428 }
1429
1430 private int getNumScreensExcludingEmptyAndCustom() {
1431 int numScrollingPages = getChildCount() - numEmptyScreensToIgnore() - numCustomPages();
1432 return numScrollingPages;
1433 }
1434
1435 public void syncWithScroll() {
1436 float offset = wallpaperOffsetForCurrentScroll();
1437 mWallpaperOffset.setFinalX(offset);
1438 updateOffset(true);
1439 }
1440
1441 public float getCurrX() {
1442 return mCurrentOffset;
1443 }
1444
1445 public float getFinalX() {
1446 return mFinalOffset;
1447 }
1448
1449 private void animateToFinal() {
1450 mAnimating = true;
1451 mAnimationStartOffset = mCurrentOffset;
1452 mAnimationStartTime = System.currentTimeMillis();
1453 }
1454
1455 private void setWallpaperOffsetSteps() {
1456 // Set wallpaper offset steps (1 / (number of screens - 1))
1457 float xOffset = 1.0f / mNumPagesForWallpaperParallax;
1458 if (xOffset != mLastSetWallpaperOffsetSteps) {
1459 mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
1460 mLastSetWallpaperOffsetSteps = xOffset;
1461 }
1462 }
1463
1464 public void setFinalX(float x) {
1465 scheduleUpdate();
1466 mFinalOffset = Math.max(0f, Math.min(x, 1.0f));
1467 if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
1468 if (mNumScreens > 0) {
1469 // Don't animate if we're going from 0 screens
1470 animateToFinal();
1471 }
1472 mNumScreens = getNumScreensExcludingEmptyAndCustom();
1473 }
1474 }
1475
1476 private void scheduleUpdate() {
1477 if (!mWaitingForUpdate) {
1478 mChoreographer.postFrameCallback(this);
1479 mWaitingForUpdate = true;
1480 }
1481 }
1482
1483 public void jumpToFinal() {
1484 mCurrentOffset = mFinalOffset;
1485 }
1486 }
1487
1488 @Override
1489 public void computeScroll() {
1490 super.computeScroll();
1491 mWallpaperOffset.syncWithScroll();
1492 }
1493
1494 @Override
1495 public void announceForAccessibility(CharSequence text) {
1496 // Don't announce if apps is on top of us.
1497 if (!mLauncher.isAllAppsVisible()) {
1498 super.announceForAccessibility(text);
1499 }
1500 }
1501
1502 void showOutlines() {
1503 if (!workspaceInModalState() && !mIsSwitchingState) {
1504 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1505 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1506 mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0🔵
1507 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1508 mChildrenOutlineFadeInAnimation.start();
1509 }
1510 }
1511
1512 void hideOutlines() {
1513 if (!workspaceInModalState() && !mIsSwitchingState) {
1514 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel();
1515 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
1516 mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.🔵
1517 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1518 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1519 mChildrenOutlineFadeOutAnimation.start();
1520 }
1521 }
1522
1523 public void showOutlinesTemporarily() {
1524 if (!mIsPageMoving && !isTouchActive()) {
1525 snapToPage(mCurrentPage);
1526 }
1527 }
1528
1529 public void setChildrenOutlineAlpha(float alpha) {
1530 mChildrenOutlineAlpha = alpha;
1531 for (int i = 0; i < getChildCount(); i++) {
1532 CellLayout cl = (CellLayout) getChildAt(i);
1533 cl.setBackgroundAlpha(alpha);
1534 }
1535 }
1536
1537 public float getChildrenOutlineAlpha() {
1538 return mChildrenOutlineAlpha;
1539 }
1540
1541 private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1542 final DragLayer dragLayer = mLauncher.getDragLayer();
1543
1544 if (mBackgroundFadeInAnimation != null) {
1545 mBackgroundFadeInAnimation.cancel();
1546 mBackgroundFadeInAnimation = null;
1547 }
1548 if (mBackgroundFadeOutAnimation != null) {
1549 mBackgroundFadeOutAnimation.cancel();
1550 mBackgroundFadeOutAnimation = null;
1551 }
1552 float startAlpha = dragLayer.getBackgroundAlpha();
1553 if (finalAlpha != startAlpha) {
1554 if (animated) {
1555 mBackgroundFadeOutAnimation =
1556 LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1557 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1558 public void onAnimationUpdate(ValueAnimator animation) {
1559 dragLayer.setBackgroundAlpha(
1560 ((Float)animation.getAnimatedValue()).floatValue());
1561 }
1562 });
1563 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5f));
1564 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1565 mBackgroundFadeOutAnimation.start();
1566 } else {
1567 dragLayer.setBackgroundAlpha(finalAlpha);
1568 }
1569 }
1570 }
1571
1572 float backgroundAlphaInterpolator(float r) {
1573 float pivotA = 0.1f;
1574 float pivotB = 0.4f;
1575 if (r < pivotA) {
1576 return 0;
1577 } else if (r > pivotB) {
1578 return 1.0f;
1579 } else {
1580 return (r - pivotA)/(pivotB - pivotA);
1581 }
1582 }
1583
1584 private void updatePageAlphaValues(int screenCenter) {
1585 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1586 if (mWorkspaceFadeInAdjacentScreens &&
1587 !workspaceInModalState() &&
1588 !mIsSwitchingState &&
1589 !isInOverscroll) {
1590 for (int i = numCustomPages(); i < getChildCount(); i++) {
1591 CellLayout child = (CellLayout) getChildAt(i);
1592 if (child != null) {
1593 float scrollProgress = getScrollProgress(screenCenter, child, i);
1594 float alpha = 1 - Math.abs(scrollProgress);
1595 child.getShortcutsAndWidgets().setAlpha(alpha);
1596 //child.setBackgroundAlphaMultiplier(1 - alpha);
1597 }
1598 }
1599 }
1600 }
1601
1602 private void setChildrenBackgroundAlphaMultipliers(float a) {
1603 for (int i = 0; i < getChildCount(); i++) {
1604 CellLayout child = (CellLayout) getChildAt(i);
1605 child.setBackgroundAlphaMultiplier(a);
1606 }
1607 }
1608
1609 public boolean hasCustomContent() {
1610 return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1611 }
1612
1613 public int numCustomPages() {
1614 return hasCustomContent() ? 1 : 0;
1615 }
1616
1617 public boolean isOnOrMovingToCustomContent() {
1618 return hasCustomContent() && getNextPage() == 0;
1619 }
1620
1621 private void updateStateForCustomContent(int screenCenter) {
1622 float translationX = 0;
1623 float progress = 0;
1624 if (hasCustomContent()) {
1625 int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1626
1627 int scrollDelta = getScrollX() - getScrollForPage(index) -
1628 getLayoutTransitionOffsetForPage(index);
1629 float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1630 translationX = scrollRange - scrollDelta;
1631 progress = (scrollRange - scrollDelta) / scrollRange;
1632
1633 if (isLayoutRtl()) {
1634 translationX = Math.min(0, translationX);
1635 } else {
1636 translationX = Math.max(0, translationX);
1637 }
1638 progress = Math.max(0, progress);
1639 }
1640
1641 if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) return;
1642
1643 CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1644 if (progress > 0 && cc.getVisibility() != VISIBLE && !workspaceInModalState()) {
1645 cc.setVisibility(VISIBLE);
1646 }
1647
1648 mLastCustomContentScrollProgress = progress;
1649
1650 mLauncher.getDragLayer().setBackgroundAlpha(progress * 0.8f);
1651
1652 if (mLauncher.getHotseat() != null) {
1653 mLauncher.getHotseat().setTranslationX(translationX);
1654 }
1655
1656 if (getPageIndicator() != null) {
1657 getPageIndicator().setTranslationX(translationX);
1658 }
1659
1660 if (mCustomContentCallbacks != null) {
1661 mCustomContentCallbacks.onScrollProgressChanged(progress);
1662 }
1663 }
1664
1665 @Override
1666 protected OnClickListener getPageIndicatorClickListener() {
1667 AccessibilityManager am = (AccessibilityManager)
1668 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1669 if (!am.isTouchExplorationEnabled()) {
1670 return null;
1671 }
1672 OnClickListener listener = new OnClickListener() {
1673 @Override
1674 public void onClick(View arg0) {
1675 enterOverviewMode();
1676 }
1677 };
1678 return listener;
1679 }
1680
1681 @Override
1682 protected void screenScrolled(int screenCenter) {
1683 final boolean isRtl = isLayoutRtl();
1684 super.screenScrolled(screenCenter);
1685
1686 updatePageAlphaValues(screenCenter);
1687 updateStateForCustomContent(screenCenter);
1688 enableHwLayersOnVisiblePages();
1689
1690 boolean shouldOverScroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
1691
1692 if (shouldOverScroll) {
1693 int index = 0;
1694 final int lowerIndex = 0;
1695 final int upperIndex = getChildCount() - 1;
1696
1697 final boolean isLeftPage = mOverScrollX < 0;
1698 index = (!isRtl && isLeftPage) || (isRtl && !isLeftPage) ? lowerIndex : upperIndex;
1699
1700 CellLayout cl = (CellLayout) getChildAt(index);
1701 float effect = Math.abs(mOverScrollEffect);
1702 cl.setOverScrollAmount(Math.abs(effect), isLeftPage);
1703
1704 mOverscrollEffectSet = true;
1705 } else {
1706 if (mOverscrollEffectSet && getChildCount() > 0) {
1707 mOverscrollEffectSet = false;
1708 ((CellLayout) getChildAt(0)).setOverScrollAmount(0, false);
1709 ((CellLayout) getChildAt(getChildCount() - 1)).setOverScrollAmount(0, false);
1710 }
1711 }
1712 }
1713
1714 @Override
1715 protected void overScroll(float amount) {
1716 boolean shouldOverScroll = (amount < 0 && (!hasCustomContent() || isLayoutRtl())) ||
1717 (amount > 0 && (!hasCustomContent() || !isLayoutRtl()));
1718 if (shouldOverScroll) {
1719 dampedOverScroll(amount);
1720 mOverScrollEffect = acceleratedOverFactor(amount);
1721 } else {
1722 mOverScrollEffect = 0;
1723 }
1724 }
1725
1726 protected void onAttachedToWindow() {
1727 super.onAttachedToWindow();
1728 mWindowToken = getWindowToken();
1729 computeScroll();
1730 mDragController.setWindowToken(mWindowToken);
1731 }
1732
1733 protected void onDetachedFromWindow() {
1734 super.onDetachedFromWindow();
1735 mWindowToken = null;
1736 }
1737
1738 protected void onResume() {
1739 if (getPageIndicator() != null) {
1740 // In case accessibility state has changed, we need to perform this on every
1741 // attach to window
1742 OnClickListener listener = getPageIndicatorClickListener();
1743 if (listener != null) {
1744 getPageIndicator().setOnClickListener(listener);
1745 }
1746 }
1747 AccessibilityManager am = (AccessibilityManager)
1748 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1749 sAccessibilityEnabled = am.isEnabled();
1750
1751 // Update wallpaper dimensions if they were changed since last onResume
1752 // (we also always set the wallpaper dimensions in the constructor)
1753 if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
1754 setWallpaperDimension();
1755 }
1756 mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
1757 // Force the wallpaper offset steps to be set again, because another app might have changed
1758 // them
1759 mLastSetWallpaperOffsetSteps = 0f;
1760 }
1761
1762 @Override
1763 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1764 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1765 mWallpaperOffset.syncWithScroll();
1766 mWallpaperOffset.jumpToFinal();
1767 }
1768 super.onLayout(changed, left, top, right, bottom);
1769 }
1770
1771 @Override
1772 protected void onDraw(Canvas canvas) {
1773 super.onDraw(canvas);
1774
1775 // Call back to LauncherModel to finish binding after the first draw
1776 post(mBindPages);
1777 }
1778
1779 @Override
1780 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1781 if (!mLauncher.isAllAppsVisible()) {
1782 final Folder openFolder = getOpenFolder();
1783 if (openFolder != null) {
1784 return openFolder.requestFocus(direction, previouslyFocusedRect);
1785 } else {
1786 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1787 }
1788 }
1789 return false;
1790 }
1791
1792 @Override
1793 public int getDescendantFocusability() {
1794 if (workspaceInModalState()) {
1795 return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1796 }
1797 return super.getDescendantFocusability();
1798 }
1799
1800 @Override
1801 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1802 if (!mLauncher.isAllAppsVisible()) {
1803 final Folder openFolder = getOpenFolder();
1804 if (openFolder != null) {
1805 openFolder.addFocusables(views, direction);
1806 } else {
1807 super.addFocusables(views, direction, focusableMode);
1808 }
1809 }
1810 }
1811
1812 public boolean workspaceInModalState() {
1813 return mState != State.NORMAL;
1814 }
1815
1816 void enableChildrenCache(int fromPage, int toPage) {
1817 if (fromPage > toPage) {
1818 final int temp = fromPage;
1819 fromPage = toPage;
1820 toPage = temp;
1821 }
1822
1823 final int screenCount = getChildCount();
1824
1825 fromPage = Math.max(fromPage, 0);
1826 toPage = Math.min(toPage, screenCount - 1);
1827
1828 for (int i = fromPage; i <= toPage; i++) {
1829 final CellLayout layout = (CellLayout) getChildAt(i);
1830 layout.setChildrenDrawnWithCacheEnabled(true);
1831 layout.setChildrenDrawingCacheEnabled(true);
1832 }
1833 }
1834
1835 void clearChildrenCache() {
1836 final int screenCount = getChildCount();
1837 for (int i = 0; i < screenCount; i++) {
1838 final CellLayout layout = (CellLayout) getChildAt(i);
1839 layout.setChildrenDrawnWithCacheEnabled(false);
1840 // In software mode, we don't want the items to continue to be drawn into bitmaps
1841 if (!isHardwareAccelerated()) {
1842 layout.setChildrenDrawingCacheEnabled(false);
1843 }
1844 }
1845 }
1846
1847 private void updateChildrenLayersEnabled(boolean force) {
1848 boolean small = mState == State.OVERVIEW || mIsSwitchingState;
1849 boolean enableChildrenLayers = force || small || mAnimatingViewIntoPlace || isPageMoving();
1850
1851 if (enableChildrenLayers != mChildrenLayersEnabled) {
1852 mChildrenLayersEnabled = enableChildrenLayers;
1853 if (mChildrenLayersEnabled) {
1854 enableHwLayersOnVisiblePages();
1855 } else {
1856 for (int i = 0; i < getPageCount(); i++) {
1857 final CellLayout cl = (CellLayout) getChildAt(i);
1858 cl.enableHardwareLayer(false);
1859 }
1860 }
1861 }
1862 }
1863
1864 private void enableHwLayersOnVisiblePages() {
1865 if (mChildrenLayersEnabled) {
1866 final int screenCount = getChildCount();
1867 getVisiblePages(mTempVisiblePagesRange);
1868 int leftScreen = mTempVisiblePagesRange[0];
1869 int rightScreen = mTempVisiblePagesRange[1];
1870 if (leftScreen == rightScreen) {
1871 // make sure we're caching at least two pages always
1872 if (rightScreen < screenCount - 1) {
1873 rightScreen++;
1874 } else if (leftScreen > 0) {
1875 leftScreen--;
1876 }
1877 }
1878
1879 final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1880 for (int i = 0; i < screenCount; i++) {
1881 final CellLayout layout = (CellLayout) getPageAt(i);
1882
1883 // enable layers between left and right screen inclusive, except for the
1884 // customScreen, which may animate its content during transitions.
1885 boolean enableLayer = layout != customScreen &&
1886 leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1887 layout.enableHardwareLayer(enableLayer);
1888 }
1889 }
1890 }
1891
1892 public void buildPageHardwareLayers() {
1893 // force layers to be enabled just for the call to buildLayer
1894 updateChildrenLayersEnabled(true);
1895 if (getWindowToken() != null) {
1896 final int childCount = getChildCount();
1897 for (int i = 0; i < childCount; i++) {
1898 CellLayout cl = (CellLayout) getChildAt(i);
1899 cl.buildHardwareLayer();
1900 }
1901 }
1902 updateChildrenLayersEnabled(false);
1903 }
1904
1905 protected void onWallpaperTap(MotionEvent ev) {
1906 final int[] position = mTempCell;
1907 getLocationOnScreen(position);
1908
1909 int pointerIndex = ev.getActionIndex();
1910 position[0] += (int) ev.getX(pointerIndex);
1911 position[1] += (int) ev.getY(pointerIndex);
1912
1913 mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1914 ev.getAction() == MotionEvent.ACTION_UP
1915 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1916 position[0], position[1], 0, null);
1917 }
1918
1919 /*
1920 * This interpolator emulates the rate at which the perceived scale of an object changes
1921 * as its distance from a camera increases. When this interpolator is applied to a scale
1922 * animation on a view, it evokes the sense that the object is shrinking due to moving away
1923 * from the camera.
1924 */
1925 static class ZInterpolator implements TimeInterpolator {
1926 private float focalLength;
1927
1928 public ZInterpolator(float foc) {
1929 focalLength = foc;
1930 }
1931
1932 public float getInterpolation(float input) {
1933 return (1.0f - focalLength / (focalLength + input)) /
1934 (1.0f - focalLength / (focalLength + 1.0f));
1935 }
1936 }
1937
1938 /*
1939 * The exact reverse of ZInterpolator.
1940 */
1941 static class InverseZInterpolator implements TimeInterpolator {
1942 private ZInterpolator zInterpolator;
1943 public InverseZInterpolator(float foc) {
1944 zInterpolator = new ZInterpolator(foc);
1945 }
1946 public float getInterpolation(float input) {
1947 return 1 - zInterpolator.getInterpolation(1 - input);
1948 }
1949 }
1950
1951 /*
1952 * ZInterpolator compounded with an ease-out.
1953 */
1954 static class ZoomOutInterpolator implements TimeInterpolator {
1955 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75f);
1956 private final ZInterpolator zInterpolator = new ZInterpolator(0.13f);
1957
1958 public float getInterpolation(float input) {
1959 return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1960 }
1961 }
1962
1963 /*
1964 * InvereZInterpolator compounded with an ease-out.
1965 */
1966 static class ZoomInInterpolator implements TimeInterpolator {
1967 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35f);
1968 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0f);
1969
1970 public float getInterpolation(float input) {
1971 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1972 }
1973 }
1974
1975 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1976
1977 /*
1978 *
1979 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1980 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1981 *
1982 * These methods mark the appropriate pages as accepting drops (which alters their visual
1983 * appearance).
1984 *
1985 */
1986 private static Rect getDrawableBounds(Drawable d) {
1987 Rect bounds = new Rect();
1988 d.copyBounds(bounds);
1989 if (bounds.width() == 0 || bounds.height() == 0) {
1990 bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
1991 } else {
1992 bounds.offsetTo(0, 0);
1993 }
1994 if (d instanceof PreloadIconDrawable) {
1995 int inset = -((PreloadIconDrawable) d).getOutset();
1996 bounds.inset(inset, inset);
1997 }
1998 return bounds;
1999 }
2000
2001 public void onExternalDragStartedWithItem(View v) {
2002 // Compose a drag bitmap with the view scaled to the icon size
2003 LauncherAppState app = LauncherAppState.getInstance();
2004 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2005 int iconSize = grid.iconSizePx;
2006 int bmpWidth = v.getMeasuredWidth();
2007 int bmpHeight = v.getMeasuredHeight();
2008
2009 // If this is a text view, use its drawable instead
2010 if (v instanceof TextView) {
2011 TextView tv = (TextView) v;
2012 Drawable d = tv.getCompoundDrawables()[1];
2013 Rect bounds = getDrawableBounds(d);
2014 bmpWidth = bounds.width();
2015 bmpHeight = bounds.height();
2016 }
2017
2018 // Compose the bitmap to create the icon from
2019 Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight,
2020 Bitmap.Config.ARGB_8888);
2021 mCanvas.setBitmap(b);
2022 drawDragView(v, mCanvas, 0);
2023 mCanvas.setBitmap(null);
2024
2025 // The outline is used to visualize where the item will land if dropped
2026 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
2027 }
2028
2029 public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
2030 int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
2031
2032 // The outline is used to visualize where the item will land if dropped
2033 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
2034 }
2035
2036 public void exitWidgetResizeMode() {
2037 DragLayer dragLayer = mLauncher.getDragLayer();
2038 dragLayer.clearAllResizeFrames();
2039 }
2040
2041 private void initAnimationArrays() {
2042 final int childCount = getChildCount();
2043 if (mLastChildCount == childCount) return;
2044
2045 mOldBackgroundAlphas = new float[childCount];
2046 mOldAlphas = new float[childCount];
2047 mNewBackgroundAlphas = new float[childCount];
2048 mNewAlphas = new float[childCount];
2049 }
2050
2051 Animator getChangeStateAnimation(final State state, boolean animated,
2052 ArrayList<View> layerViews) {
2053 return getChangeStateAnimation(state, animated, 0, -1, layerViews);
2054 }
2055
2056 @Override
2057 protected void getFreeScrollPageRange(int[] range) {
2058 getOverviewModePages(range);
2059 }
2060
2061 private void getOverviewModePages(int[] range) {
2062 int start = numCustomPages();
2063 int end = getChildCount() - 1;
2064
2065 range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
2066 range[1] = Math.max(0, end);
2067 }
2068
2069 protected void onStartReordering() {
2070 super.onStartReordering();
2071 showOutlines();
2072 // Reordering handles its own animations, disable the automatic ones.
2073 disableLayoutTransitions();
2074 }
2075
2076 protected void onEndReordering() {
2077 super.onEndReordering();
2078
2079 if (mLauncher.isWorkspaceLoading()) {
2080 // Invalid and dangerous operation if workspace is loading
2081 return;
2082 }
2083
2084 hideOutlines();
2085 mScreenOrder.clear();
2086 int count = getChildCount();
2087 for (int i = 0; i < count; i++) {
2088 CellLayout cl = ((CellLayout) getChildAt(i));
2089 mScreenOrder.add(getIdForScreen(cl));
2090 }
2091
2092 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
2093
2094 // Re-enable auto layout transitions for page deletion.
2095 enableLayoutTransitions();
2096 }
2097
2098 public boolean isInOverviewMode() {
2099 return mState == State.OVERVIEW;
2100 }
2101
2102 public boolean enterOverviewMode() {
2103 if (mTouchState != TOUCH_STATE_REST) {
2104 return false;
2105 }
2106 enableOverviewMode(true, -1, true);
2107 return true;
2108 }
2109
2110 public void exitOverviewMode(boolean animated) {
2111 exitOverviewMode(-1, animated);
2112 }
2113
2114 public void exitOverviewMode(int snapPage, boolean animated) {
2115 enableOverviewMode(false, snapPage, animated);
2116 }
2117
2118 private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
2119 State finalState = Workspace.State.OVERVIEW;
2120 if (!enable) {
2121 finalState = Workspace.State.NORMAL;
2122 }
2123
2124 Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
2125 if (workspaceAnim != null) {
2126 onTransitionPrepare();
2127 workspaceAnim.addListener(new AnimatorListenerAdapter() {
2128 @Override
2129 public void onAnimationEnd(Animator arg0) {
2130 onTransitionEnd();
2131 }
2132 });
2133 workspaceAnim.start();
2134 }
2135 }
2136
2137 int getOverviewModeTranslationY() {
2138 LauncherAppState app = LauncherAppState.getInstance();
2139 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2140 Rect overviewBar = grid.getOverviewModeButtonBarRect();
2141
2142 int availableHeight = getViewportHeight();
2143 int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
2144 int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
2145 int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
2146 - scaledHeight) / 2;
2147
2148 return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
2149 }
2150
2151 boolean shouldVoiceButtonProxyBeVisible() {
2152 if (isOnOrMovingToCustomContent()) {
2153 return false;
2154 }
2155 if (mState != State.NORMAL) {
2156 return false;
2157 }
2158 return true;
2159 }
2160
2161 public void updateInteractionForState() {
2162 if (mState != State.NORMAL) {
2163 mLauncher.onInteractionBegin();
2164 } else {
2165 mLauncher.onInteractionEnd();
2166 }
2167 }
2168
2169 private void setState(State state) {
2170 mState = state;
2171 updateInteractionForState();
2172 updateAccessibilityFlags();
2173 }
2174
2175 State getState() {
2176 return mState;
2177 }
2178
2179 private void updateAccessibilityFlags() {
2180 int accessible = mState == State.NORMAL ?
2181 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
2182 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2183 setImportantForAccessibility(accessible);
2184 }
2185
2186 private static final int HIDE_WORKSPACE_DURATION = 100;
2187
2188 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
2189 return getChangeStateAnimation(state, animated, delay, snapPage, null);
2190 }
2191
2192 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage,
2193 ArrayList<View> layerViews) {
2194 if (mState == state) {
2195 return null;
2196 }
2197
2198 // Initialize animation arrays for the first time if necessary
2199 initAnimationArrays();
2200
2201 AnimatorSet anim = animated ? LauncherAnimUtils.createAnimatorSet() : null;
2202
2203 final State oldState = mState;
2204 final boolean oldStateIsNormal = (oldState == State.NORMAL);
2205 final boolean oldStateIsSpringLoaded = (oldState == State.SPRING_LOADED);
2206 final boolean oldStateIsNormalHidden = (oldState == State.NORMAL_HIDDEN);
2207 final boolean oldStateIsOverviewHidden = (oldState == State.OVERVIEW_HIDDEN);
2208 final boolean oldStateIsOverview = (oldState == State.OVERVIEW);
2209 setState(state);
2210 final boolean stateIsNormal = (state == State.NORMAL);
2211 final boolean stateIsSpringLoaded = (state == State.SPRING_LOADED);
2212 final boolean stateIsNormalHidden = (state == State.NORMAL_HIDDEN);
2213 final boolean stateIsOverviewHidden = (state == State.OVERVIEW_HIDDEN);
2214 final boolean stateIsOverview = (state == State.OVERVIEW);
2215 float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0f : 0f;
2216 float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1f : 0f;
2217 float finalOverviewPanelAlpha = stateIsOverview ? 1f : 0f;
2218 float finalSearchBarAlpha = !stateIsNormal ? 0f : 1f;
2219 float finalWorkspaceTranslationY = stateIsOverview || stateIsOverviewHidden ?
2220 getOverviewModeTranslationY() : 0;
2221
2222 boolean workspaceToAllApps = (oldStateIsNormal && stateIsNormalHidden);
2223 boolean overviewToAllApps = (oldStateIsOverview && stateIsOverviewHidden);
2224 boolean allAppsToWorkspace = (stateIsNormalHidden && stateIsNormal);
2225 boolean workspaceToOverview = (oldStateIsNormal && stateIsOverview);
2226 boolean overviewToWorkspace = (oldStateIsOverview && stateIsNormal);
2227
2228 mNewScale = 1.0f;
2229
2230 if (oldStateIsOverview) {
2231 disableFreeScroll();
2232 } else if (stateIsOverview) {
2233 enableFreeScroll();
2234 }
2235
2236 if (state != State.NORMAL) {
2237 if (stateIsSpringLoaded) {
2238 mNewScale = mSpringLoadedShrinkFactor;
2239 } else if (stateIsOverview || stateIsOverviewHidden) {
2240 mNewScale = mOverviewModeShrinkFactor;
2241 }
2242 }
2243
2244 final int duration;
2245 if (workspaceToAllApps || overviewToAllApps) {
2246 duration = HIDE_WORKSPACE_DURATION; //getResources().getInteger(R.integer.config_workspaceUns🔵
2247 } else if (workspaceToOverview || overviewToWorkspace) {
2248 duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
2249 } else {
2250 duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
2251 }
2252
2253 if (snapPage == -1) {
2254 snapPage = getPageNearestToCenterOfScreen();
2255 }
2256 snapToPage(snapPage, duration, mZoomInInterpolator);
2257
2258 for (int i = 0; i < getChildCount(); i++) {
2259 final CellLayout cl = (CellLayout) getChildAt(i);
2260 boolean isCurrentPage = (i == snapPage);
2261 float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
2262 float finalAlpha;
2263 if (stateIsNormalHidden || stateIsOverviewHidden) {
2264 finalAlpha = 0f;
2265 } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
2266 finalAlpha = (i == snapPage || i < numCustomPages()) ? 1f : 0f;
2267 } else {
2268 finalAlpha = 1f;
2269 }
2270
2271 // If we are animating to/from the small state, then hide the side pages and fade the
2272 // current page in
2273 if (!mIsSwitchingState) {
2274 if (workspaceToAllApps || allAppsToWorkspace) {
2275 if (allAppsToWorkspace && isCurrentPage) {
2276 initialAlpha = 0f;
2277 } else if (!isCurrentPage) {
2278 initialAlpha = finalAlpha = 0f;
2279 }
2280 cl.setShortcutAndWidgetAlpha(initialAlpha);
2281 }
2282 }
2283
2284 mOldAlphas[i] = initialAlpha;
2285 mNewAlphas[i] = finalAlpha;
2286 if (animated) {
2287 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
2288 mNewBackgroundAlphas[i] = finalBackgroundAlpha;
2289 } else {
2290 cl.setBackgroundAlpha(finalBackgroundAlpha);
2291 cl.setShortcutAndWidgetAlpha(finalAlpha);
2292 }
2293 }
2294
2295 final View searchBar = mLauncher.getQsbBar();
2296 final View overviewPanel = mLauncher.getOverviewPanel();
2297 final View hotseat = mLauncher.getHotseat();
2298 final View pageIndicator = getPageIndicator();
2299 if (animated) {
2300 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
2301 scale.scaleX(mNewScale)
2302 .scaleY(mNewScale)
2303 .translationY(finalWorkspaceTranslationY)
2304 .setDuration(duration)
2305 .setInterpolator(mZoomInInterpolator);
2306 anim.play(scale);
2307 for (int index = 0; index < getChildCount(); index++) {
2308 final int i = index;
2309 final CellLayout cl = (CellLayout) getChildAt(i);
2310 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
2311 if (mOldAlphas[i] == 0 && mNewAlphas[i] == 0) {
2312 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
2313 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
2314 } else {
2315 if (layerViews != null) {
2316 layerViews.add(cl);
2317 }
2318 if (mOldAlphas[i] != mNewAlphas[i] || currentAlpha != mNewAlphas[i]) {
2319 LauncherViewPropertyAnimator alphaAnim =
2320 new LauncherViewPropertyAnimator(cl.getShortcutsAndWidgets());
2321 alphaAnim.alpha(mNewAlphas[i])
2322 .setDuration(duration)
2323 .setInterpolator(mZoomInInterpolator);
2324 anim.play(alphaAnim);
2325 }
2326 if (mOldBackgroundAlphas[i] != 0 ||
2327 mNewBackgroundAlphas[i] != 0) {
2328 ValueAnimator bgAnim =
2329 LauncherAnimUtils.ofFloat(cl, 0f, 1f);
2330 bgAnim.setInterpolator(mZoomInInterpolator);
2331 bgAnim.setDuration(duration);
2332 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2333 public void onAnimationUpdate(float a, float b) {
2334 cl.setBackgroundAlpha(
2335 a * mOldBackgroundAlphas[i] +
2336 b * mNewBackgroundAlphas[i]);
2337 }
2338 });
2339 anim.play(bgAnim);
2340 }
2341 }
2342 }
2343 Animator pageIndicatorAlpha = null;
2344 if (pageIndicator != null) {
2345 pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator)
2346 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2347 pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
2348 } else {
2349 // create a dummy animation so we don't need to do null checks later
2350 pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
2351 }
2352
2353 Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat)
2354 .alpha(finalHotseatAndPageIndicatorAlpha).withLayer();
2355 hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2356
2357 Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar)
2358 .alpha(finalSearchBarAlpha).withLayer();
2359 searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2360
2361 Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel)
2362 .alpha(finalOverviewPanelAlpha).withLayer();
2363 overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2364
2365 // For animation optimations, we may need to provide the Launcher transition
2366 // with a set of views on which to force build layers in certain scenarios.
2367 hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2368 searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2369 overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2370 if (layerViews != null) {
2371 layerViews.add(hotseat);
2372 layerViews.add(searchBar);
2373 layerViews.add(overviewPanel);
2374 }
2375
2376 if (workspaceToOverview) {
2377 pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
2378 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
2379 overviewPanelAlpha.setInterpolator(null);
2380 } else if (overviewToWorkspace) {
2381 pageIndicatorAlpha.setInterpolator(null);
2382 hotseatAlpha.setInterpolator(null);
2383 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
2384 }
2385
2386 overviewPanelAlpha.setDuration(duration);
2387 pageIndicatorAlpha.setDuration(duration);
2388 hotseatAlpha.setDuration(duration);
2389 searchBarAlpha.setDuration(duration);
2390
2391 anim.play(overviewPanelAlpha);
2392 anim.play(hotseatAlpha);
2393 anim.play(searchBarAlpha);
2394 anim.play(pageIndicatorAlpha);
2395 anim.setStartDelay(delay);
2396 } else {
2397 overviewPanel.setAlpha(finalOverviewPanelAlpha);
2398 AlphaUpdateListener.updateVisibility(overviewPanel);
2399 hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2400 AlphaUpdateListener.updateVisibility(hotseat);
2401 if (pageIndicator != null) {
2402 pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
2403 AlphaUpdateListener.updateVisibility(pageIndicator);
2404 }
2405 searchBar.setAlpha(finalSearchBarAlpha);
2406 AlphaUpdateListener.updateVisibility(searchBar);
2407 updateCustomContentVisibility();
2408 setScaleX(mNewScale);
2409 setScaleY(mNewScale);
2410 setTranslationY(finalWorkspaceTranslationY);
2411 }
2412 mLauncher.updateVoiceButtonProxyVisible(false);
2413
2414 if (stateIsNormal) {
2415 animateBackgroundGradient(0f, animated);
2416 } else {
2417 animateBackgroundGradient(getResources().getInteger(
2418 R.integer.config_workspaceScrimAlpha) / 100f, animated);
2419 }
2420 return anim;
2421 }
2422
2423 static class AlphaUpdateListener implements AnimatorUpdateListener, AnimatorListener {
2424 View view;
2425 public AlphaUpdateListener(View v) {
2426 view = v;
2427 }
2428
2429 @Override
2430 public void onAnimationUpdate(ValueAnimator arg0) {
2431 updateVisibility(view);
2432 }
2433
2434 public static void updateVisibility(View view) {
2435 // We want to avoid the extra layout pass by setting the views to GONE unless
2436 // accessibility is on, in which case not setting them to GONE causes a glitch.
2437 int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
2438 if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
2439 view.setVisibility(invisibleState);
2440 } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
2441 && view.getVisibility() != VISIBLE) {
2442 view.setVisibility(VISIBLE);
2443 }
2444 }
2445
2446 @Override
2447 public void onAnimationCancel(Animator arg0) {
2448 }
2449
2450 @Override
2451 public void onAnimationEnd(Animator arg0) {
2452 updateVisibility(view);
2453 }
2454
2455 @Override
2456 public void onAnimationRepeat(Animator arg0) {
2457 }
2458
2459 @Override
2460 public void onAnimationStart(Animator arg0) {
2461 // We want the views to be visible for animation, so fade-in/out is visible
2462 view.setVisibility(VISIBLE);
2463 }
2464 }
2465
2466 @Override
2467 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2468 onTransitionPrepare();
2469 }
2470
2471 @Override
2472 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2473 }
2474
2475 @Override
2476 public void onLauncherTransitionStep(Launcher l, float t) {
2477 mTransitionProgress = t;
2478 }
2479
2480 @Override
2481 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2482 onTransitionEnd();
2483 }
2484
2485 private void onTransitionPrepare() {
2486 mIsSwitchingState = true;
2487
2488 // Invalidate here to ensure that the pages are rendered during the state change transition.
2489 invalidate();
2490
2491 updateChildrenLayersEnabled(false);
2492 hideCustomContentIfNecessary();
2493 }
2494
2495 void updateCustomContentVisibility() {
2496 int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2497 if (hasCustomContent()) {
2498 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2499 }
2500 }
2501
2502 void showCustomContentIfNecessary() {
2503 boolean show = mState == Workspace.State.NORMAL;
2504 if (show && hasCustomContent()) {
2505 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2506 }
2507 }
2508
2509 void hideCustomContentIfNecessary() {
2510 boolean hide = mState != Workspace.State.NORMAL;
2511 if (hide && hasCustomContent()) {
2512 disableLayoutTransitions();
2513 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2514 enableLayoutTransitions();
2515 }
2516 }
2517
2518 private void onTransitionEnd() {
2519 mIsSwitchingState = false;
2520 updateChildrenLayersEnabled(false);
2521 showCustomContentIfNecessary();
2522 }
2523
2524 @Override
2525 public View getContent() {
2526 return this;
2527 }
2528
2529 /**
2530 * Draw the View v into the given Canvas.
2531 *
2532 * @param v the view to draw
2533 * @param destCanvas the canvas to draw on
2534 * @param padding the horizontal and vertical padding to use when drawing
2535 */
2536 private static void drawDragView(View v, Canvas destCanvas, int padding) {
2537 final Rect clipRect = sTempRect;
2538 v.getDrawingRect(clipRect);
2539
2540 boolean textVisible = false;
2541
2542 destCanvas.save();
2543 if (v instanceof TextView) {
2544 Drawable d = ((TextView) v).getCompoundDrawables()[1];
2545 Rect bounds = getDrawableBounds(d);
2546 clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
2547 destCanvas.translate(padding / 2 - bounds.left, padding / 2 - bounds.top);
2548 d.draw(destCanvas);
2549 } else {
2550 if (v instanceof FolderIcon) {
2551 // For FolderIcons the text can bleed into the icon area, and so we need to
2552 // hide the text completely (which can't be achieved by clipping).
2553 if (((FolderIcon) v).getTextVisible()) {
2554 ((FolderIcon) v).setTextVisible(false);
2555 textVisible = true;
2556 }
2557 }
2558 destCanvas.translate(-v.getScrollX() + padding / 2, -v.getScrollY() + padding / 2);
2559 destCanvas.clipRect(clipRect, Op.REPLACE);
2560 v.draw(destCanvas);
2561
2562 // Restore text visibility of FolderIcon if necessary
2563 if (textVisible) {
2564 ((FolderIcon) v).setTextVisible(true);
2565 }
2566 }
2567 destCanvas.restore();
2568 }
2569
2570 /**
2571 * Returns a new bitmap to show when the given View is being dragged around.
2572 * Responsibility for the bitmap is transferred to the caller.
2573 * @param expectedPadding padding to add to the drag view. If a different padding was used
2574 * its value will be changed
2575 */
2576 public Bitmap createDragBitmap(View v, AtomicInteger expectedPadding) {
2577 Bitmap b;
2578
2579 int padding = expectedPadding.get();
2580 if (v instanceof TextView) {
2581 Drawable d = ((TextView) v).getCompoundDrawables()[1];
2582 Rect bounds = getDrawableBounds(d);
2583 b = Bitmap.createBitmap(bounds.width() + padding,
2584 bounds.height() + padding, Bitmap.Config.ARGB_8888);
2585 expectedPadding.set(padding - bounds.left - bounds.top);
2586 } else {
2587 b = Bitmap.createBitmap(
2588 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2589 }
2590
2591 mCanvas.setBitmap(b);
2592 drawDragView(v, mCanvas, padding);
2593 mCanvas.setBitmap(null);
2594
2595 return b;
2596 }
2597
2598 /**
2599 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2600 * Responsibility for the bitmap is transferred to the caller.
2601 */
2602 private Bitmap createDragOutline(View v, int padding) {
2603 final int outlineColor = getResources().getColor(R.color.outline_color);
2604 final Bitmap b = Bitmap.createBitmap(
2605 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
2606
2607 mCanvas.setBitmap(b);
2608 drawDragView(v, mCanvas, padding);
2609 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor);
2610 mCanvas.setBitmap(null);
2611 return b;
2612 }
2613
2614 /**
2615 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2616 * Responsibility for the bitmap is transferred to the caller.
2617 */
2618 private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h,
2619 boolean clipAlpha) {
2620 final int outlineColor = getResources().getColor(R.color.outline_color);
2621 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2622 mCanvas.setBitmap(b);
2623
2624 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2625 float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
2626 (h - padding) / (float) orig.getHeight());
2627 int scaledWidth = (int) (scaleFactor * orig.getWidth());
2628 int scaledHeight = (int) (scaleFactor * orig.getHeight());
2629 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2630
2631 // center the image
2632 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2633
2634 mCanvas.drawBitmap(orig, src, dst, null);
2635 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor,
2636 clipAlpha);
2637 mCanvas.setBitmap(null);
2638
2639 return b;
2640 }
2641
2642 void startDrag(CellLayout.CellInfo cellInfo) {
2643 View child = cellInfo.cell;
2644
2645 // Make sure the drag was started by a long press as opposed to a long click.
2646 if (!child.isInTouchMode()) {
2647 return;
2648 }
2649
2650 mDragInfo = cellInfo;
2651 child.setVisibility(INVISIBLE);
2652 CellLayout layout = (CellLayout) child.getParent().getParent();
2653 layout.prepareChildForDrag(child);
2654
2655 beginDragShared(child, this);
2656 }
2657
2658 public void beginDragShared(View child, DragSource source) {
2659 child.clearFocus();
2660 child.setPressed(false);
2661
2662 // The outline is used to visualize where the item will land if dropped
2663 mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
2664
2665 mLauncher.onDragStarted(child);
2666 // The drag bitmap follows the touch point around on the screen
2667 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2668 final Bitmap b = createDragBitmap(child, padding);
2669
2670 final int bmpWidth = b.getWidth();
2671 final int bmpHeight = b.getHeight();
2672
2673 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2674 int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2675 int dragLayerY = Math.round(mTempXY[1] - (bmpHeight - scale * bmpHeight) / 2
2676 - padding.get() / 2);
2677
2678 LauncherAppState app = LauncherAppState.getInstance();
2679 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2680 Point dragVisualizeOffset = null;
2681 Rect dragRect = null;
2682 if (child instanceof BubbleTextView) {
2683 int iconSize = grid.iconSizePx;
2684 int top = child.getPaddingTop();
2685 int left = (bmpWidth - iconSize) / 2;
2686 int right = left + iconSize;
2687 int bottom = top + iconSize;
2688 dragLayerY += top;
2689 // Note: The drag region is used to calculate drag layer offsets, but the
2690 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2691 dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2692 dragRect = new Rect(left, top, right, bottom);
2693 } else if (child instanceof FolderIcon) {
2694 int previewSize = grid.folderIconSizePx;
2695 dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2696 }
2697
2698 // Clear the pressed state if necessary
2699 if (child instanceof BubbleTextView) {
2700 BubbleTextView icon = (BubbleTextView) child;
2701 icon.clearPressedBackground();
2702 }
2703
2704 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2705 String msg = "Drag started with a view that has no tag set. This "
2706 + "will cause a crash (issue 11627249) down the line. "
2707 + "View: " + child + " tag: " + child.getTag();
2708 throw new IllegalStateException(msg);
2709 }
2710
2711 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2712 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2713 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2714
2715 if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2716 mDragSourceInternal = (ShortcutAndWidgetContainer) child.getParent();
2717 }
2718
2719 b.recycle();
2720 }
2721
2722 public void beginExternalDragShared(View child, DragSource source) {
2723 LauncherAppState app = LauncherAppState.getInstance();
2724 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2725 int iconSize = grid.iconSizePx;
2726
2727 // Notify launcher of drag start
2728 mLauncher.onDragStarted(child);
2729
2730 // Compose a new drag bitmap that is of the icon size
2731 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2732 final Bitmap tmpB = createDragBitmap(child, padding);
2733 Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
2734 Paint p = new Paint();
2735 p.setFilterBitmap(true);
2736 mCanvas.setBitmap(b);
2737 mCanvas.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()),
2738 new Rect(0, 0, iconSize, iconSize), p);
2739 mCanvas.setBitmap(null);
2740
2741 // Find the child's location on the screen
2742 int bmpWidth = tmpB.getWidth();
2743 float iconScale = (float) bmpWidth / iconSize;
2744 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY) * iconScale;
2745 int dragLayerX = Math.round(mTempXY[0] - (bmpWidth - scale * child.getWidth()) / 2);
2746 int dragLayerY = Math.round(mTempXY[1]);
2747
2748 // Note: The drag region is used to calculate drag layer offsets, but the
2749 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2750 Point dragVisualizeOffset = new Point(-padding.get() / 2, padding.get() / 2);
2751 Rect dragRect = new Rect(0, 0, iconSize, iconSize);
2752
2753 if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
2754 String msg = "Drag started with a view that has no tag set. This "
2755 + "will cause a crash (issue 11627249) down the line. "
2756 + "View: " + child + " tag: " + child.getTag();
2757 throw new IllegalStateException(msg);
2758 }
2759
2760 // Start the drag
2761 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
2762 DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale);
2763 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2764
2765 // Recycle temporary bitmaps
2766 tmpB.recycle();
2767 }
2768
2769 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2770 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2771 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2772
2773 final int[] cellXY = new int[2];
2774 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2775 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2776
2777 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2778 cellXY[1]);
2779 }
2780
2781 public boolean transitionStateShouldAllowDrop() {
2782 return ((!isSwitchingState() || mTransitionProgress > 0.5f) &&
2783 (mState == State.NORMAL || mState == State.SPRING_LOADED));
2784 }
2785
2786 /**
2787 * {@inheritDoc}
2788 */
2789 public boolean acceptDrop(DragObject d) {
2790 // If it's an external drop (e.g. from All Apps), check if it should be accepted
2791 CellLayout dropTargetLayout = mDropToLayout;
2792 if (d.dragSource != this) {
2793 // Don't accept the drop if we're not over a screen at time of drop
2794 if (dropTargetLayout == null) {
2795 return false;
2796 }
2797 if (!transitionStateShouldAllowDrop()) return false;
2798
2799 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
2800 d.dragView, mDragViewVisualCenter);
2801
2802 // We want the point to be mapped to the dragTarget.
2803 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2804 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2805 } else {
2806 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2807 }
2808
2809 int spanX = 1;
2810 int spanY = 1;
2811 if (mDragInfo != null) {
2812 final CellLayout.CellInfo dragCellInfo = mDragInfo;
2813 spanX = dragCellInfo.spanX;
2814 spanY = dragCellInfo.spanY;
2815 } else {
2816 final ItemInfo dragInfo = (ItemInfo) d.dragInfo;
2817 spanX = dragInfo.spanX;
2818 spanY = dragInfo.spanY;
2819 }
2820
2821 int minSpanX = spanX;
2822 int minSpanY = spanY;
2823 if (d.dragInfo instanceof PendingAddWidgetInfo) {
2824 minSpanX = ((PendingAddWidgetInfo) d.dragInfo).minSpanX;
2825 minSpanY = ((PendingAddWidgetInfo) d.dragInfo).minSpanY;
2826 }
2827
2828 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
2829 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, dropTargetLayout,
2830 mTargetCell);
2831 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
2832 mDragViewVisualCenter[1], mTargetCell);
2833 if (mCreateUserFolderOnDrop && willCreateUserFolder((ItemInfo) d.dragInfo,
2834 dropTargetLayout, mTargetCell, distance, true)) {
2835 return true;
2836 }
2837
2838 if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder((ItemInfo) d.dragInfo,
2839 dropTargetLayout, mTargetCell, distance)) {
2840 return true;
2841 }
2842
2843 int[] resultSpan = new int[2];
2844 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
2845 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
2846 null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP);
2847 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
2848
2849 // Don't accept the drop if there's no room for the item
2850 if (!foundCell) {
2851 // Don't show the message if we are dropping on the AllApps button and the hotseat
2852 // is full
2853 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2854 if (mTargetCell != null && isHotseat) {
2855 Hotseat hotseat = mLauncher.getHotseat();
2856 if (hotseat.isAllAppsButtonRank(
2857 hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell[1]))) {
2858 return false;
2859 }
2860 }
2861
2862 mLauncher.showOutOfSpaceMessage(isHotseat);
2863 return false;
2864 }
2865 }
2866
2867 long screenId = getIdForScreen(dropTargetLayout);
2868 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2869 commitExtraEmptyScreen();
2870 }
2871
2872 return true;
2873 }
2874
2875 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2876 distance, boolean considerTimeout) {
2877 if (distance > mMaxDistanceForFolderCreation) return false;
2878 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2879
2880 if (dropOverView != null) {
2881 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2882 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2883 return false;
2884 }
2885 }
2886
2887 boolean hasntMoved = false;
2888 if (mDragInfo != null) {
2889 hasntMoved = dropOverView == mDragInfo.cell;
2890 }
2891
2892 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2893 return false;
2894 }
2895
2896 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2897 boolean willBecomeShortcut =
2898 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2899 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2900
2901 return (aboveShortcut && willBecomeShortcut);
2902 }
2903
2904 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2905 float distance) {
2906 if (distance > mMaxDistanceForFolderCreation) return false;
2907 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2908
2909 if (dropOverView != null) {
2910 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2911 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2912 return false;
2913 }
2914 }
2915
2916 if (dropOverView instanceof FolderIcon) {
2917 FolderIcon fi = (FolderIcon) dropOverView;
2918 if (fi.acceptDrop(dragInfo)) {
2919 return true;
2920 }
2921 }
2922 return false;
2923 }
2924
2925 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2926 int[] targetCell, float distance, boolean external, DragView dragView,
2927 Runnable postAnimationRunnable) {
2928 if (distance > mMaxDistanceForFolderCreation) return false;
2929 View v = target.getChildAt(targetCell[0], targetCell[1]);
2930
2931 boolean hasntMoved = false;
2932 if (mDragInfo != null) {
2933 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2934 hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2935 mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2936 }
2937
2938 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2939 mCreateUserFolderOnDrop = false;
2940 final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2941
2942 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2943 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2944
2945 if (aboveShortcut && willBecomeShortcut) {
2946 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2947 ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2948 // if the drag started here, we need to remove it from the workspace
2949 if (!external) {
2950 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2951 }
2952
2953 Rect folderLocation = new Rect();
2954 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2955 target.removeView(v);
2956
2957 FolderIcon fi =
2958 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2959 destInfo.cellX = -1;
2960 destInfo.cellY = -1;
2961 sourceInfo.cellX = -1;
2962 sourceInfo.cellY = -1;
2963
2964 // If the dragView is null, we can't animate
2965 boolean animate = dragView != null;
2966 if (animate) {
2967 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2968 postAnimationRunnable);
2969 } else {
2970 fi.addItem(destInfo);
2971 fi.addItem(sourceInfo);
2972 }
2973 return true;
2974 }
2975 return false;
2976 }
2977
2978 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2979 float distance, DragObject d, boolean external) {
2980 if (distance > mMaxDistanceForFolderCreation) return false;
2981
2982 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2983 if (!mAddToExistingFolderOnDrop) return false;
2984 mAddToExistingFolderOnDrop = false;
2985
2986 if (dropOverView instanceof FolderIcon) {
2987 FolderIcon fi = (FolderIcon) dropOverView;
2988 if (fi.acceptDrop(d.dragInfo)) {
2989 fi.onDrop(d);
2990
2991 // if the drag started here, we need to remove it from the workspace
2992 if (!external) {
2993 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2994 }
2995 return true;
2996 }
2997 }
2998 return false;
2999 }
3000
3001 public void onDrop(final DragObject d) {
3002 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
3003 mDragViewVisualCenter);
3004
3005 CellLayout dropTargetLayout = mDropToLayout;
3006
3007 // We want the point to be mapped to the dragTarget.
3008 if (dropTargetLayout != null) {
3009 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
3010 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3011 } else {
3012 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
3013 }
3014 }
3015
3016 int snapScreen = -1;
3017 boolean resizeOnDrop = false;
3018 if (d.dragSource != this) {
3019 final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
3020 (int) mDragViewVisualCenter[1] };
3021 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
3022 } else if (mDragInfo != null) {
3023 final View cell = mDragInfo.cell;
3024
3025 Runnable resizeRunnable = null;
3026 if (dropTargetLayout != null && !d.cancelled) {
3027 // Move internally
3028 boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
3029 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
3030 long container = hasMovedIntoHotseat ?
3031 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3032 LauncherSettings.Favorites.CONTAINER_DESKTOP;
3033 long screenId = (mTargetCell[0] < 0) ?
3034 mDragInfo.screenId : getIdForScreen(dropTargetLayout);
3035 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
3036 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
3037 // First we find the cell nearest to point at which the item is
3038 // dropped, without any consideration to whether there is an item there.
3039
3040 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
3041 mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
3042 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3043 mDragViewVisualCenter[1], mTargetCell);
3044
3045 // If the item being dropped is a shortcut and the nearest drop
3046 // cell also contains a shortcut, then create a folder with the two shortcuts.
3047 if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
3048 dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
3049 return;
3050 }
3051
3052 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
3053 distance, d, false)) {
3054 return;
3055 }
3056
3057 // Aside from the special case where we're dropping a shortcut onto a shortcut,
3058 // we need to find the nearest cell location that is vacant
3059 ItemInfo item = (ItemInfo) d.dragInfo;
3060 int minSpanX = item.spanX;
3061 int minSpanY = item.spanY;
3062 if (item.minSpanX > 0 && item.minSpanY > 0) {
3063 minSpanX = item.minSpanX;
3064 minSpanY = item.minSpanY;
3065 }
3066
3067 int[] resultSpan = new int[2];
3068 mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3069 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell,
3070 mTargetCell, resultSpan, CellLayout.MODE_ON_DROP);
3071
3072 boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0;
3073
3074 // if the widget resizes on drop
3075 if (foundCell && (cell instanceof AppWidgetHostView) &&
3076 (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY)) {
3077 resizeOnDrop = true;
3078 item.spanX = resultSpan[0];
3079 item.spanY = resultSpan[1];
3080 AppWidgetHostView awhv = (AppWidgetHostView) cell;
3081 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0],
3082 resultSpan[1]);
3083 }
3084
3085 if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
3086 snapScreen = getPageIndexForScreenId(screenId);
3087 snapToPage(snapScreen);
3088 }
3089
3090 if (foundCell) {
3091 final ItemInfo info = (ItemInfo) cell.getTag();
3092 if (hasMovedLayouts) {
3093 // Reparent the view
3094 CellLayout parentCell = getParentCellLayoutForView(cell);
3095 if (parentCell != null) {
3096 parentCell.removeView(cell);
3097 } else if (LauncherAppState.isDogfoodBuild()) {
3098 throw new NullPointerException("mDragInfo.cell has null parent");
3099 }
3100 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
3101 info.spanX, info.spanY);
3102 }
3103
3104 // update the item's position after drop
3105 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
3106 lp.cellX = lp.tmpCellX = mTargetCell[0];
3107 lp.cellY = lp.tmpCellY = mTargetCell[1];
3108 lp.cellHSpan = item.spanX;
3109 lp.cellVSpan = item.spanY;
3110 lp.isLockedToGrid = true;
3111
3112 if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
3113 cell instanceof LauncherAppWidgetHostView) {
3114 final CellLayout cellLayout = dropTargetLayout;
3115 // We post this call so that the widget has a chance to be placed
3116 // in its final location
3117
3118 final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
3119 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
3120 if (pinfo != null &&
3121 pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
3122 final Runnable addResizeFrame = new Runnable() {
3123 public void run() {
3124 DragLayer dragLayer = mLauncher.getDragLayer();
3125 dragLayer.addResizeFrame(info, hostView, cellLayout);
3126 }
3127 };
3128 resizeRunnable = (new Runnable() {
3129 public void run() {
3130 if (!isPageMoving()) {
3131 addResizeFrame.run();
3132 } else {
3133 mDelayedResizeRunnable = addResizeFrame;
3134 }
3135 }
3136 });
3137 }
3138 }
3139
3140 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX,
3141 lp.cellY, item.spanX, item.spanY);
3142 } else {
3143 // If we can't find a drop location, we return the item to its original position
3144 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
3145 mTargetCell[0] = lp.cellX;
3146 mTargetCell[1] = lp.cellY;
3147 CellLayout layout = (CellLayout) cell.getParent().getParent();
3148 layout.markCellsAsOccupiedForView(cell);
3149 }
3150 }
3151
3152 final CellLayout parent = (CellLayout) cell.getParent().getParent();
3153 final Runnable finalResizeRunnable = resizeRunnable;
3154 // Prepare it to be animated into its new position
3155 // This must be called after the view has been re-parented
3156 final Runnable onCompleteRunnable = new Runnable() {
3157 @Override
3158 public void run() {
3159 mAnimatingViewIntoPlace = false;
3160 updateChildrenLayersEnabled(false);
3161 if (finalResizeRunnable != null) {
3162 finalResizeRunnable.run();
3163 }
3164 }
3165 };
3166 mAnimatingViewIntoPlace = true;
3167 if (d.dragView.hasDrawn()) {
3168 final ItemInfo info = (ItemInfo) cell.getTag();
3169 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET) {
3170 int animationType = resizeOnDrop ? ANIMATE_INTO_POSITION_AND_RESIZE :
3171 ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3172 animateWidgetDrop(info, parent, d.dragView,
3173 onCompleteRunnable, animationType, cell, false);
3174 } else {
3175 int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
3176 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
3177 onCompleteRunnable, this);
3178 }
3179 } else {
3180 d.deferDragViewCleanupPostAnimation = false;
3181 cell.setVisibility(VISIBLE);
3182 }
3183 parent.onDropChild(cell);
3184 }
3185 }
3186
3187 public void setFinalScrollForPageChange(int pageIndex) {
3188 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3189 if (cl != null) {
3190 mSavedScrollX = getScrollX();
3191 mSavedTranslationX = cl.getTranslationX();
3192 mSavedRotationY = cl.getRotationY();
3193 final int newX = getScrollForPage(pageIndex);
3194 setScrollX(newX);
3195 cl.setTranslationX(0f);
3196 cl.setRotationY(0f);
3197 }
3198 }
3199
3200 public void resetFinalScrollForPageChange(int pageIndex) {
3201 if (pageIndex >= 0) {
3202 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3203 setScrollX(mSavedScrollX);
3204 cl.setTranslationX(mSavedTranslationX);
3205 cl.setRotationY(mSavedRotationY);
3206 }
3207 }
3208
3209 public void getViewLocationRelativeToSelf(View v, int[] location) {
3210 getLocationInWindow(location);
3211 int x = location[0];
3212 int y = location[1];
3213
3214 v.getLocationInWindow(location);
3215 int vX = location[0];
3216 int vY = location[1];
3217
3218 location[0] = vX - x;
3219 location[1] = vY - y;
3220 }
3221
3222 public void onDragEnter(DragObject d) {
3223 mDragEnforcer.onDragEnter();
3224 mCreateUserFolderOnDrop = false;
3225 mAddToExistingFolderOnDrop = false;
3226
3227 mDropToLayout = null;
3228 CellLayout layout = getCurrentDropLayout();
3229 setCurrentDropLayout(layout);
3230 setCurrentDragOverlappingLayout(layout);
3231
3232 if (!workspaceInModalState()) {
3233 mLauncher.getDragLayer().showPageHints();
3234 }
3235 }
3236
3237 /** Return a rect that has the cellWidth/cellHeight (left, top), and
3238 * widthGap/heightGap (right, bottom) */
3239 static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
3240 LauncherAppState app = LauncherAppState.getInstance();
3241 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3242
3243 Display display = launcher.getWindowManager().getDefaultDisplay();
3244 Point smallestSize = new Point();
3245 Point largestSize = new Point();
3246 display.getCurrentSizeRange(smallestSize, largestSize);
3247 int countX = (int) grid.numColumns;
3248 int countY = (int) grid.numRows;
3249 if (orientation == CellLayout.LANDSCAPE) {
3250 if (mLandscapeCellLayoutMetrics == null) {
3251 Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
3252 int width = largestSize.x - padding.left - padding.right;
3253 int height = smallestSize.y - padding.top - padding.bottom;
3254 mLandscapeCellLayoutMetrics = new Rect();
3255 mLandscapeCellLayoutMetrics.set(
3256 grid.calculateCellWidth(width, countX),
3257 grid.calculateCellHeight(height, countY), 0, 0);
3258 }
3259 return mLandscapeCellLayoutMetrics;
3260 } else if (orientation == CellLayout.PORTRAIT) {
3261 if (mPortraitCellLayoutMetrics == null) {
3262 Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
3263 int width = smallestSize.x - padding.left - padding.right;
3264 int height = largestSize.y - padding.top - padding.bottom;
3265 mPortraitCellLayoutMetrics = new Rect();
3266 mPortraitCellLayoutMetrics.set(
3267 grid.calculateCellWidth(width, countX),
3268 grid.calculateCellHeight(height, countY), 0, 0);
3269 }
3270 return mPortraitCellLayoutMetrics;
3271 }
3272 return null;
3273 }
3274
3275 public void onDragExit(DragObject d) {
3276 mDragEnforcer.onDragExit();
3277
3278 // Here we store the final page that will be dropped to, if the workspace in fact
3279 // receives the drop
3280 if (mInScrollArea) {
3281 if (isPageMoving()) {
3282 // If the user drops while the page is scrolling, we should use that page as the
3283 // destination instead of the page that is being hovered over.
3284 mDropToLayout = (CellLayout) getPageAt(getNextPage());
3285 } else {
3286 mDropToLayout = mDragOverlappingLayout;
3287 }
3288 } else {
3289 mDropToLayout = mDragTargetLayout;
3290 }
3291
3292 if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
3293 mCreateUserFolderOnDrop = true;
3294 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
3295 mAddToExistingFolderOnDrop = true;
3296 }
3297
3298 // Reset the scroll area and previous drag target
3299 onResetScrollArea();
3300 setCurrentDropLayout(null);
3301 setCurrentDragOverlappingLayout(null);
3302
3303 mSpringLoadedDragController.cancel();
3304
3305 if (!mIsPageMoving) {
3306 hideOutlines();
3307 }
3308 mLauncher.getDragLayer().hidePageHints();
3309 }
3310
3311 void setCurrentDropLayout(CellLayout layout) {
3312 if (mDragTargetLayout != null) {
3313 mDragTargetLayout.revertTempState();
3314 mDragTargetLayout.onDragExit();
3315 }
3316 mDragTargetLayout = layout;
3317 if (mDragTargetLayout != null) {
3318 mDragTargetLayout.onDragEnter();
3319 }
3320 cleanupReorder(true);
3321 cleanupFolderCreation();
3322 setCurrentDropOverCell(-1, -1);
3323 }
3324
3325 void setCurrentDragOverlappingLayout(CellLayout layout) {
3326 if (mDragOverlappingLayout != null) {
3327 mDragOverlappingLayout.setIsDragOverlapping(false);
3328 }
3329 mDragOverlappingLayout = layout;
3330 if (mDragOverlappingLayout != null) {
3331 mDragOverlappingLayout.setIsDragOverlapping(true);
3332 }
3333 invalidate();
3334 }
3335
3336 void setCurrentDropOverCell(int x, int y) {
3337 if (x != mDragOverX || y != mDragOverY) {
3338 mDragOverX = x;
3339 mDragOverY = y;
3340 setDragMode(DRAG_MODE_NONE);
3341 }
3342 }
3343
3344 void setDragMode(int dragMode) {
3345 if (dragMode != mDragMode) {
3346 if (dragMode == DRAG_MODE_NONE) {
3347 cleanupAddToFolder();
3348 // We don't want to cancel the re-order alarm every time the target cell changes
3349 // as this feels to slow / unresponsive.
3350 cleanupReorder(false);
3351 cleanupFolderCreation();
3352 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3353 cleanupReorder(true);
3354 cleanupFolderCreation();
3355 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3356 cleanupAddToFolder();
3357 cleanupReorder(true);
3358 } else if (dragMode == DRAG_MODE_REORDER) {
3359 cleanupAddToFolder();
3360 cleanupFolderCreation();
3361 }
3362 mDragMode = dragMode;
3363 }
3364 }
3365
3366 private void cleanupFolderCreation() {
3367 if (mDragFolderRingAnimator != null) {
3368 mDragFolderRingAnimator.animateToNaturalState();
3369 mDragFolderRingAnimator = null;
3370 }
3371 mFolderCreationAlarm.setOnAlarmListener(null);
3372 mFolderCreationAlarm.cancelAlarm();
3373 }
3374
3375 private void cleanupAddToFolder() {
3376 if (mDragOverFolderIcon != null) {
3377 mDragOverFolderIcon.onDragExit(null);
3378 mDragOverFolderIcon = null;
3379 }
3380 }
3381
3382 private void cleanupReorder(boolean cancelAlarm) {
3383 // Any pending reorders are canceled
3384 if (cancelAlarm) {
3385 mReorderAlarm.cancelAlarm();
3386 }
3387 mLastReorderX = -1;
3388 mLastReorderY = -1;
3389 }
3390
3391 /*
3392 *
3393 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3394 * coordinate space. The argument xy is modified with the return result.
3395 *
3396 * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3397 * computing it itself; we use this to avoid redundant matrix inversions in
3398 * findMatchingPageForDragOver
3399 *
3400 */
3401 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3402 xy[0] = xy[0] - v.getLeft();
3403 xy[1] = xy[1] - v.getTop();
3404 }
3405
3406 boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3407 if (r == null) {
3408 r = new Rect();
3409 }
3410 mTempPt[0] = x;
3411 mTempPt[1] = y;
3412 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3413
3414 LauncherAppState app = LauncherAppState.getInstance();
3415 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3416 r = grid.getHotseatRect();
3417 if (r.contains(mTempPt[0], mTempPt[1])) {
3418 return true;
3419 }
3420 return false;
3421 }
3422
3423 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3424 mTempPt[0] = (int) xy[0];
3425 mTempPt[1] = (int) xy[1];
3426 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3427 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3428
3429 xy[0] = mTempPt[0];
3430 xy[1] = mTempPt[1];
3431 }
3432
3433 /*
3434 *
3435 * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3436 * the parent View's coordinate space. The argument xy is modified with the return result.
3437 *
3438 */
3439 void mapPointFromChildToSelf(View v, float[] xy) {
3440 xy[0] += v.getLeft();
3441 xy[1] += v.getTop();
3442 }
3443
3444 static private float squaredDistance(float[] point1, float[] point2) {
3445 float distanceX = point1[0] - point2[0];
3446 float distanceY = point2[1] - point2[1];
3447 return distanceX * distanceX + distanceY * distanceY;
3448 }
3449
3450 /*
3451 *
3452 * This method returns the CellLayout that is currently being dragged to. In order to drag
3453 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3454 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3455 *
3456 * Return null if no CellLayout is currently being dragged over
3457 *
3458 */
3459 private CellLayout findMatchingPageForDragOver(
3460 DragView dragView, float originX, float originY, boolean exact) {
3461 // We loop through all the screens (ie CellLayouts) and see which ones overlap
3462 // with the item being dragged and then choose the one that's closest to the touch point
3463 final int screenCount = getChildCount();
3464 CellLayout bestMatchingScreen = null;
3465 float smallestDistSoFar = Float.MAX_VALUE;
3466
3467 for (int i = 0; i < screenCount; i++) {
3468 // The custom content screen is not a valid drag over option
3469 if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3470 continue;
3471 }
3472
3473 CellLayout cl = (CellLayout) getChildAt(i);
3474
3475 final float[] touchXy = {originX, originY};
3476 // Transform the touch coordinates to the CellLayout's local coordinates
3477 // If the touch point is within the bounds of the cell layout, we can return immediately
3478 cl.getMatrix().invert(mTempInverseMatrix);
3479 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3480
3481 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3482 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3483 return cl;
3484 }
3485
3486 if (!exact) {
3487 // Get the center of the cell layout in screen coordinates
3488 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3489 cellLayoutCenter[0] = cl.getWidth()/2;
3490 cellLayoutCenter[1] = cl.getHeight()/2;
3491 mapPointFromChildToSelf(cl, cellLayoutCenter);
3492
3493 touchXy[0] = originX;
3494 touchXy[1] = originY;
3495
3496 // Calculate the distance between the center of the CellLayout
3497 // and the touch point
3498 float dist = squaredDistance(touchXy, cellLayoutCenter);
3499
3500 if (dist < smallestDistSoFar) {
3501 smallestDistSoFar = dist;
3502 bestMatchingScreen = cl;
3503 }
3504 }
3505 }
3506 return bestMatchingScreen;
3507 }
3508
3509 // This is used to compute the visual center of the dragView. This point is then
3510 // used to visualize drop locations and determine where to drop an item. The idea is that
3511 // the visual center represents the user's interpretation of where the item is, and hence
3512 // is the appropriate point to use when determining drop location.
3513 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3514 DragView dragView, float[] recycle) {
3515 float res[];
3516 if (recycle == null) {
3517 res = new float[2];
3518 } else {
3519 res = recycle;
3520 }
3521
3522 // First off, the drag view has been shifted in a way that is not represented in the
3523 // x and y values or the x/yOffsets. Here we account for that shift.
3524 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3525 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3526
3527 // These represent the visual top and left of drag view if a dragRect was provided.
3528 // If a dragRect was not provided, then they correspond to the actual view left and
3529 // top, as the dragRect is in that case taken to be the entire dragView.
3530 // R.dimen.dragViewOffsetY.
3531 int left = x - xOffset;
3532 int top = y - yOffset;
3533
3534 // In order to find the visual center, we shift by half the dragRect
3535 res[0] = left + dragView.getDragRegion().width() / 2;
3536 res[1] = top + dragView.getDragRegion().height() / 2;
3537
3538 return res;
3539 }
3540
3541 private boolean isDragWidget(DragObject d) {
3542 return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3543 d.dragInfo instanceof PendingAddWidgetInfo);
3544 }
3545 private boolean isExternalDragWidget(DragObject d) {
3546 return d.dragSource != this && isDragWidget(d);
3547 }
3548
3549 public void onDragOver(DragObject d) {
3550 // Skip drag over events while we are dragging over side pages
3551 if (mInScrollArea || !transitionStateShouldAllowDrop()) return;
3552
3553 Rect r = new Rect();
3554 CellLayout layout = null;
3555 ItemInfo item = (ItemInfo) d.dragInfo;
3556 if (item == null) {
3557 if (LauncherAppState.isDogfoodBuild()) {
3558 throw new NullPointerException("DragObject has null info");
3559 }
3560 return;
3561 }
3562
3563 // Ensure that we have proper spans for the item that we are dropping
3564 if (item.spanX < 0 || item.spanY < 0) throw new RuntimeException("Improper spans found");
3565 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset,
3566 d.dragView, mDragViewVisualCenter);
3567
3568 final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3569 // Identify whether we have dragged over a side page
3570 if (workspaceInModalState()) {
3571 if (mLauncher.getHotseat() != null && !isExternalDragWidget(d)) {
3572 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3573 layout = mLauncher.getHotseat().getLayout();
3574 }
3575 }
3576 if (layout == null) {
3577 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3578 }
3579 if (layout != mDragTargetLayout) {
3580 setCurrentDropLayout(layout);
3581 setCurrentDragOverlappingLayout(layout);
3582
3583 boolean isInSpringLoadedMode = (mState == State.SPRING_LOADED);
3584 if (isInSpringLoadedMode) {
3585 if (mLauncher.isHotseatLayout(layout)) {
3586 mSpringLoadedDragController.cancel();
3587 } else {
3588 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3589 }
3590 }
3591 }
3592 } else {
3593 // Test to see if we are over the hotseat otherwise just use the current page
3594 if (mLauncher.getHotseat() != null && !isDragWidget(d)) {
3595 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3596 layout = mLauncher.getHotseat().getLayout();
3597 }
3598 }
3599 if (layout == null) {
3600 layout = getCurrentDropLayout();
3601 }
3602 if (layout != mDragTargetLayout) {
3603 setCurrentDropLayout(layout);
3604 setCurrentDragOverlappingLayout(layout);
3605 }
3606 }
3607
3608 // Handle the drag over
3609 if (mDragTargetLayout != null) {
3610 // We want the point to be mapped to the dragTarget.
3611 if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3612 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3613 } else {
3614 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3615 }
3616
3617 ItemInfo info = (ItemInfo) d.dragInfo;
3618
3619 int minSpanX = item.spanX;
3620 int minSpanY = item.spanY;
3621 if (item.minSpanX > 0 && item.minSpanY > 0) {
3622 minSpanX = item.minSpanX;
3623 minSpanY = item.minSpanY;
3624 }
3625
3626 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3627 (int) mDragViewVisualCenter[1], minSpanX, minSpanY,
3628 mDragTargetLayout, mTargetCell);
3629 int reorderX = mTargetCell[0];
3630 int reorderY = mTargetCell[1];
3631
3632 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3633
3634 float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
3635 mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
3636
3637 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0],
3638 mTargetCell[1]);
3639
3640 manageFolderFeedback(info, mDragTargetLayout, mTargetCell,
3641 targetCellDistance, dragOverView);
3642
3643 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
3644 mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
3645 item.spanY, child, mTargetCell);
3646
3647 if (!nearestDropOccupied) {
3648 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3649 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3650 mTargetCell[0], mTargetCell[1], item.spanX, item.spanY, false,
3651 d.dragView.getDragVisualizeOffset(), d.dragView.getDragRegion());
3652 } else if ((mDragMode == DRAG_MODE_NONE || mDragMode == DRAG_MODE_REORDER)
3653 && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX ||
3654 mLastReorderY != reorderY)) {
3655
3656 int[] resultSpan = new int[2];
3657 mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3658 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY,
3659 child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT);
3660
3661 // Otherwise, if we aren't adding to or creating a folder and there's no pending
3662 // reorder, then we schedule a reorder
3663 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter,
3664 minSpanX, minSpanY, item.spanX, item.spanY, d.dragView, child);
3665 mReorderAlarm.setOnAlarmListener(listener);
3666 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3667 }
3668
3669 if (mDragMode == DRAG_MODE_CREATE_FOLDER || mDragMode == DRAG_MODE_ADD_TO_FOLDER ||
3670 !nearestDropOccupied) {
3671 if (mDragTargetLayout != null) {
3672 mDragTargetLayout.revertTempState();
3673 }
3674 }
3675 }
3676 }
3677
3678 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3679 int[] targetCell, float distance, View dragOverView) {
3680 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3681 false);
3682
3683 if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3684 !mFolderCreationAlarm.alarmPending()) {
3685 mFolderCreationAlarm.setOnAlarmListener(new
3686 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3687 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3688 return;
3689 }
3690
3691 boolean willAddToFolder =
3692 willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3693
3694 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3695 mDragOverFolderIcon = ((FolderIcon) dragOverView);
3696 mDragOverFolderIcon.onDragEnter(info);
3697 if (targetLayout != null) {
3698 targetLayout.clearDragOutlines();
3699 }
3700 setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3701 return;
3702 }
3703
3704 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3705 setDragMode(DRAG_MODE_NONE);
3706 }
3707 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3708 setDragMode(DRAG_MODE_NONE);
3709 }
3710
3711 return;
3712 }
3713
3714 class FolderCreationAlarmListener implements OnAlarmListener {
3715 CellLayout layout;
3716 int cellX;
3717 int cellY;
3718
3719 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3720 this.layout = layout;
3721 this.cellX = cellX;
3722 this.cellY = cellY;
3723 }
3724
3725 public void onAlarm(Alarm alarm) {
3726 if (mDragFolderRingAnimator != null) {
3727 // This shouldn't happen ever, but just in case, make sure we clean up the mess.
3728 mDragFolderRingAnimator.animateToNaturalState();
3729 }
3730 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3731 mDragFolderRingAnimator.setCell(cellX, cellY);
3732 mDragFolderRingAnimator.setCellLayout(layout);
3733 mDragFolderRingAnimator.animateToAcceptState();
3734 layout.showFolderAccept(mDragFolderRingAnimator);
3735 layout.clearDragOutlines();
3736 setDragMode(DRAG_MODE_CREATE_FOLDER);
3737 }
3738 }
3739
3740 class ReorderAlarmListener implements OnAlarmListener {
3741 float[] dragViewCenter;
3742 int minSpanX, minSpanY, spanX, spanY;
3743 DragView dragView;
3744 View child;
3745
3746 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX,
3747 int spanY, DragView dragView, View child) {
3748 this.dragViewCenter = dragViewCenter;
3749 this.minSpanX = minSpanX;
3750 this.minSpanY = minSpanY;
3751 this.spanX = spanX;
3752 this.spanY = spanY;
3753 this.child = child;
3754 this.dragView = dragView;
3755 }
3756
3757 public void onAlarm(Alarm alarm) {
3758 int[] resultSpan = new int[2];
3759 mTargetCell = findNearestArea((int) mDragViewVisualCenter[0],
3760 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, mDragTargetLayout,
3761 mTargetCell);
3762 mLastReorderX = mTargetCell[0];
3763 mLastReorderY = mTargetCell[1];
3764
3765 mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0],
3766 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY,
3767 child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER);
3768
3769 if (mTargetCell[0] < 0 || mTargetCell[1] < 0) {
3770 mDragTargetLayout.revertTempState();
3771 } else {
3772 setDragMode(DRAG_MODE_REORDER);
3773 }
3774
3775 boolean resize = resultSpan[0] != spanX || resultSpan[1] != spanY;
3776 mDragTargetLayout.visualizeDropLocation(child, mDragOutline,
3777 (int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1],
3778 mTargetCell[0], mTargetCell[1], resultSpan[0], resultSpan[1], resize,
3779 dragView.getDragVisualizeOffset(), dragView.getDragRegion());
3780 }
3781 }
3782
3783 @Override
3784 public void getHitRectRelativeToDragLayer(Rect outRect) {
3785 // We want the workspace to have the whole area of the display (it will find the correct
3786 // cell layout to drop to in the existing drag/drop logic.
3787 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3788 }
3789
3790 /**
3791 * Add the item specified by dragInfo to the given layout.
3792 * @return true if successful
3793 */
3794 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3795 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3796 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3797 return true;
3798 }
3799 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3800 return false;
3801 }
3802
3803 private void onDropExternal(int[] touchXY, Object dragInfo,
3804 CellLayout cellLayout, boolean insertAtFirst) {
3805 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3806 }
3807
3808 /**
3809 * Drop an item that didn't originate on one of the workspace screens.
3810 * It may have come from Launcher (e.g. from all apps or customize), or it may have
3811 * come from another app altogether.
3812 *
3813 * NOTE: This can also be called when we are outside of a drag event, when we want
3814 * to add an item to one of the workspace screens.
3815 */
3816 private void onDropExternal(final int[] touchXY, final Object dragInfo,
3817 final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
3818 final Runnable exitSpringLoadedRunnable = new Runnable() {
3819 @Override
3820 public void run() {
3821 mLauncher.exitSpringLoadedDragModeDelayed(true,
3822 Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
3823 }
3824 };
3825
3826 ItemInfo info = (ItemInfo) dragInfo;
3827 int spanX = info.spanX;
3828 int spanY = info.spanY;
3829 if (mDragInfo != null) {
3830 spanX = mDragInfo.spanX;
3831 spanY = mDragInfo.spanY;
3832 }
3833
3834 final long container = mLauncher.isHotseatLayout(cellLayout) ?
3835 LauncherSettings.Favorites.CONTAINER_HOTSEAT :
3836 LauncherSettings.Favorites.CONTAINER_DESKTOP;
3837 final long screenId = getIdForScreen(cellLayout);
3838 if (!mLauncher.isHotseatLayout(cellLayout)
3839 && screenId != getScreenIdForPageIndex(mCurrentPage)
3840 && mState != State.SPRING_LOADED) {
3841 snapToScreenId(screenId, null);
3842 }
3843
3844 if (info instanceof PendingAddItemInfo) {
3845 final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;
3846
3847 boolean findNearestVacantCell = true;
3848 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
3849 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3850 cellLayout, mTargetCell);
3851 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3852 mDragViewVisualCenter[1], mTargetCell);
3853 if (willCreateUserFolder((ItemInfo) d.dragInfo, cellLayout, mTargetCell,
3854 distance, true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
3855 cellLayout, mTargetCell, distance)) {
3856 findNearestVacantCell = false;
3857 }
3858 }
3859
3860 final ItemInfo item = (ItemInfo) d.dragInfo;
3861 boolean updateWidgetSize = false;
3862 if (findNearestVacantCell) {
3863 int minSpanX = item.spanX;
3864 int minSpanY = item.spanY;
3865 if (item.minSpanX > 0 && item.minSpanY > 0) {
3866 minSpanX = item.minSpanX;
3867 minSpanY = item.minSpanY;
3868 }
3869 int[] resultSpan = new int[2];
3870 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3871 (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY,
3872 null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL);
3873
3874 if (resultSpan[0] != item.spanX || resultSpan[1] != item.spanY) {
3875 updateWidgetSize = true;
3876 }
3877 item.spanX = resultSpan[0];
3878 item.spanY = resultSpan[1];
3879 }
3880
3881 Runnable onAnimationCompleteRunnable = new Runnable() {
3882 @Override
3883 public void run() {
3884 // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
3885 // adding an item that may not be dropped right away (due to a config activity)
3886 // we defer the removal until the activity returns.
3887 deferRemoveExtraEmptyScreen();
3888
3889 // When dragging and dropping from customization tray, we deal with creating
3890 // widgets/shortcuts/folders in a slightly different way
3891 switch (pendingInfo.itemType) {
3892 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
3893 int span[] = new int[2];
3894 span[0] = item.spanX;
3895 span[1] = item.spanY;
3896 mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
3897 container, screenId, mTargetCell, span, null);
3898 break;
3899 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3900 mLauncher.processShortcutFromDrop(pendingInfo.componentName,
3901 container, screenId, mTargetCell, null);
3902 break;
3903 default:
3904 throw new IllegalStateException("Unknown item type: " +
3905 pendingInfo.itemType);
3906 }
3907 }
3908 };
3909 View finalView = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
3910 ? ((PendingAddWidgetInfo) pendingInfo).boundWidget : null;
3911
3912 if (finalView instanceof AppWidgetHostView && updateWidgetSize) {
3913 AppWidgetHostView awhv = (AppWidgetHostView) finalView;
3914 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX,
3915 item.spanY);
3916 }
3917
3918 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3919 if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
3920 ((PendingAddWidgetInfo) pendingInfo).info.configure != null) {
3921 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3922 }
3923 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
3924 animationStyle, finalView, true);
3925 } else {
3926 // This is for other drag/drop cases, like dragging from All Apps
3927 View view = null;
3928
3929 switch (info.itemType) {
3930 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
3931 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
3932 if (info.container == NO_ID && info instanceof AppInfo) {
3933 // Came from all apps -- make a copy
3934 info = new ShortcutInfo((AppInfo) info);
3935 }
3936 view = mLauncher.createShortcut(R.layout.application, cellLayout,
3937 (ShortcutInfo) info);
3938 break;
3939 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
3940 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
3941 (FolderInfo) info, mIconCache);
3942 break;
3943 default:
3944 throw new IllegalStateException("Unknown item type: " + info.itemType);
3945 }
3946
3947 // First we find the cell nearest to point at which the item is
3948 // dropped, without any consideration to whether there is an item there.
3949 if (touchXY != null) {
3950 mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
3951 cellLayout, mTargetCell);
3952 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
3953 mDragViewVisualCenter[1], mTargetCell);
3954 d.postAnimationRunnable = exitSpringLoadedRunnable;
3955 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
3956 true, d.dragView, d.postAnimationRunnable)) {
3957 return;
3958 }
3959 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
3960 true)) {
3961 return;
3962 }
3963 }
3964
3965 if (touchXY != null) {
3966 // when dragging and dropping, just find the closest free spot
3967 mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0],
3968 (int) mDragViewVisualCenter[1], 1, 1, 1, 1,
3969 null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL);
3970 } else {
3971 cellLayout.findCellForSpan(mTargetCell, 1, 1);
3972 }
3973 // Add the item to DB before adding to screen ensures that the container and other
3974 // values of the info is properly updated.
3975 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
3976 mTargetCell[0], mTargetCell[1]);
3977
3978 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
3979 info.spanY, insertAtFirst);
3980 cellLayout.onDropChild(view);
3981 cellLayout.getShortcutsAndWidgets().measureChild(view);
3982
3983 if (d.dragView != null) {
3984 // We wrap the animation call in the temporary set and reset of the current
3985 // cellLayout to its final transform -- this means we animate the drag view to
3986 // the correct final location.
3987 setFinalTransitionTransform(cellLayout);
3988 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
3989 exitSpringLoadedRunnable, this);
3990 resetTransitionTransform(cellLayout);
3991 }
3992 }
3993 }
3994
3995 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3996 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX,
3997 widgetInfo.spanY, widgetInfo, false);
3998 int visibility = layout.getVisibility();
3999 layout.setVisibility(VISIBLE);
4000
4001 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
4002 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
4003 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
4004 Bitmap.Config.ARGB_8888);
4005 mCanvas.setBitmap(b);
4006
4007 layout.measure(width, height);
4008 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
4009 layout.draw(mCanvas);
4010 mCanvas.setBitmap(null);
4011 layout.setVisibility(visibility);
4012 return b;
4013 }
4014
4015 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
4016 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
4017 boolean external, boolean scale) {
4018 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
4019 // location and size on the home screen.
4020 int spanX = info.spanX;
4021 int spanY = info.spanY;
4022
4023 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
4024 loc[0] = r.left;
4025 loc[1] = r.top;
4026
4027 setFinalTransitionTransform(layout);
4028 float cellLayoutScale =
4029 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
4030 resetTransitionTransform(layout);
4031
4032 float dragViewScaleX;
4033 float dragViewScaleY;
4034 if (scale) {
4035 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
4036 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
4037 } else {
4038 dragViewScaleX = 1f;
4039 dragViewScaleY = 1f;
4040 }
4041
4042 // The animation will scale the dragView about its center, so we need to center about
4043 // the final location.
4044 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
4045 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
4046
4047 scaleXY[0] = dragViewScaleX * cellLayoutScale;
4048 scaleXY[1] = dragViewScaleY * cellLayoutScale;
4049 }
4050
4051 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
4052 final Runnable onCompleteRunnable, int animationType, final View finalView,
4053 boolean external) {
4054 Rect from = new Rect();
4055 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
4056
4057 int[] finalPos = new int[2];
4058 float scaleXY[] = new float[2];
4059 boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
4060 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
4061 external, scalePreview);
4062
4063 Resources res = mLauncher.getResources();
4064 final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
4065
4066 // In the case where we've prebound the widget, we remove it from the DragLayer
4067 if (finalView instanceof AppWidgetHostView && external) {
4068 Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
4069 mLauncher.getDragLayer().removeView(finalView);
4070 }
4071 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
4072 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
4073 dragView.setCrossFadeBitmap(crossFadeBitmap);
4074 dragView.crossFade((int) (duration * 0.8f));
4075 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
4076 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
4077 }
4078
4079 DragLayer dragLayer = mLauncher.getDragLayer();
4080 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
4081 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
4082 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
4083 } else {
4084 int endStyle;
4085 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
4086 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
4087 } else {
4088 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
4089 }
4090
4091 Runnable onComplete = new Runnable() {
4092 @Override
4093 public void run() {
4094 if (finalView != null) {
4095 finalView.setVisibility(VISIBLE);
4096 }
4097 if (onCompleteRunnable != null) {
4098 onCompleteRunnable.run();
4099 }
4100 }
4101 };
4102 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
4103 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
4104 duration, this);
4105 }
4106 }
4107
4108 public void setFinalTransitionTransform(CellLayout layout) {
4109 if (isSwitchingState()) {
4110 mCurrentScale = getScaleX();
4111 setScaleX(mNewScale);
4112 setScaleY(mNewScale);
4113 }
4114 }
4115 public void resetTransitionTransform(CellLayout layout) {
4116 if (isSwitchingState()) {
4117 setScaleX(mCurrentScale);
4118 setScaleY(mCurrentScale);
4119 }
4120 }
4121
4122 /**
4123 * Return the current {@link CellLayout}, correctly picking the destination
4124 * screen while a scroll is in progress.
4125 */
4126 public CellLayout getCurrentDropLayout() {
4127 return (CellLayout) getChildAt(getNextPage());
4128 }
4129
4130 /**
4131 * Return the current CellInfo describing our current drag; this method exists
4132 * so that Launcher can sync this object with the correct info when the activity is created/
4133 * destroyed
4134 *
4135 */
4136 public CellLayout.CellInfo getDragInfo() {
4137 return mDragInfo;
4138 }
4139
4140 public int getCurrentPageOffsetFromCustomContent() {
4141 return getNextPage() - numCustomPages();
4142 }
4143
4144 /**
4145 * Calculate the nearest cell where the given object would be dropped.
4146 *
4147 * pixelX and pixelY should be in the coordinate system of layout
4148 */
4149 private int[] findNearestArea(int pixelX, int pixelY,
4150 int spanX, int spanY, CellLayout layout, int[] recycle) {
4151 return layout.findNearestArea(
4152 pixelX, pixelY, spanX, spanY, recycle);
4153 }
4154
4155 void setup(DragController dragController) {
4156 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
4157 mDragController = dragController;
4158
4159 // hardware layers on children are enabled on startup, but should be disabled until
4160 // needed
4161 updateChildrenLayersEnabled(false);
4162 }
4163
4164 /**
4165 * Called at the end of a drag which originated on the workspace.
4166 */
4167 public void onDropCompleted(final View target, final DragObject d,
4168 final boolean isFlingToDelete, final boolean success) {
4169 if (mDeferDropAfterUninstall) {
4170 mDeferredAction = new Runnable() {
4171 public void run() {
4172 onDropCompleted(target, d, isFlingToDelete, success);
4173 mDeferredAction = null;
4174 }
4175 };
4176 return;
4177 }
4178
4179 boolean beingCalledAfterUninstall = mDeferredAction != null;
4180
4181 if (success && !(beingCalledAfterUninstall && !mUninstallSuccessful)) {
4182 if (target != this && mDragInfo != null) {
4183 CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
4184 if (parentCell != null) {
4185 parentCell.removeView(mDragInfo.cell);
4186 } else if (LauncherAppState.isDogfoodBuild()) {
4187 throw new NullPointerException("mDragInfo.cell has null parent");
4188 }
4189 if (mDragInfo.cell instanceof DropTarget) {
4190 mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
4191 }
4192 }
4193 } else if (mDragInfo != null) {
4194 CellLayout cellLayout;
4195 if (mLauncher.isHotseatLayout(target)) {
4196 cellLayout = mLauncher.getHotseat().getLayout();
4197 } else {
4198 cellLayout = getScreenWithId(mDragInfo.screenId);
4199 }
4200 if (cellLayout == null && LauncherAppState.isDogfoodBuild()) {
4201 throw new RuntimeException("Invalid state: cellLayout == null in "
4202 + "Workspace#onDropCompleted. Please file a bug. ");
4203 }
4204 if (cellLayout != null) {
4205 cellLayout.onDropChild(mDragInfo.cell);
4206 }
4207 }
4208 if ((d.cancelled || (beingCalledAfterUninstall && !mUninstallSuccessful))
4209 && mDragInfo.cell != null) {
4210 mDragInfo.cell.setVisibility(VISIBLE);
4211 }
4212 mDragOutline = null;
4213 mDragInfo = null;
4214 }
4215
4216 public void deferCompleteDropAfterUninstallActivity() {
4217 mDeferDropAfterUninstall = true;
4218 }
4219
4220 /// maybe move this into a smaller part
4221 public void onUninstallActivityReturned(boolean success) {
4222 mDeferDropAfterUninstall = false;
4223 mUninstallSuccessful = success;
4224 if (mDeferredAction != null) {
4225 mDeferredAction.run();
4226 }
4227 }
4228
4229 void updateItemLocationsInDatabase(CellLayout cl) {
4230 int count = cl.getShortcutsAndWidgets().getChildCount();
4231
4232 long screenId = getIdForScreen(cl);
4233 int container = Favorites.CONTAINER_DESKTOP;
4234
4235 if (mLauncher.isHotseatLayout(cl)) {
4236 screenId = -1;
4237 container = Favorites.CONTAINER_HOTSEAT;
4238 }
4239
4240 for (int i = 0; i < count; i++) {
4241 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4242 ItemInfo info = (ItemInfo) v.getTag();
4243 // Null check required as the AllApps button doesn't have an item info
4244 if (info != null && info.requiresDbUpdate) {
4245 info.requiresDbUpdate = false;
4246 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
4247 info.cellY, info.spanX, info.spanY);
4248 }
4249 }
4250 }
4251
4252 ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplic🔵
4253 ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
4254 getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, fals🔵
4255 int count = getChildCount();
4256 for (int i = 0; i < count; i++) {
4257 CellLayout cl = (CellLayout) getChildAt(i);
4258 getUniqueIntents(cl, uniqueIntents, duplicates, false);
4259 }
4260 return uniqueIntents;
4261 }
4262
4263 void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
4264 ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
4265 int count = cl.getShortcutsAndWidgets().getChildCount();
4266
4267 ArrayList<View> children = new ArrayList<View>();
4268 for (int i = 0; i < count; i++) {
4269 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4270 children.add(v);
4271 }
4272
4273 for (int i = 0; i < count; i++) {
4274 View v = children.get(i);
4275 ItemInfo info = (ItemInfo) v.getTag();
4276 // Null check required as the AllApps button doesn't have an item info
4277 if (info instanceof ShortcutInfo) {
4278 ShortcutInfo si = (ShortcutInfo) info;
4279 ComponentName cn = si.intent.getComponent();
4280
4281 Uri dataUri = si.intent.getData();
4282 // If dataUri is not null / empty or if this component isn't one that would
4283 // have previously showed up in the AllApps list, then this is a widget-type
4284 // shortcut, so ignore it.
4285 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4286 continue;
4287 }
4288
4289 if (!uniqueIntents.contains(cn)) {
4290 uniqueIntents.add(cn);
4291 } else {
4292 if (stripDuplicates) {
4293 cl.removeViewInLayout(v);
4294 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4295 }
4296 if (duplicates != null) {
4297 duplicates.add(cn);
4298 }
4299 }
4300 }
4301 if (v instanceof FolderIcon) {
4302 FolderIcon fi = (FolderIcon) v;
4303 ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
4304 for (int j = 0; j < items.size(); j++) {
4305 if (items.get(j).getTag() instanceof ShortcutInfo) {
4306 ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
4307 ComponentName cn = si.intent.getComponent();
4308
4309 Uri dataUri = si.intent.getData();
4310 // If dataUri is not null / empty or if this component isn't one that would
4311 // have previously showed up in the AllApps list, then this is a widget-type
4312 // shortcut, so ignore it.
4313 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4314 continue;
4315 }
4316
4317 if (!uniqueIntents.contains(cn)) {
4318 uniqueIntents.add(cn);
4319 } else {
4320 if (stripDuplicates) {
4321 fi.getFolderInfo().remove(si);
4322 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4323 }
4324 if (duplicates != null) {
4325 duplicates.add(cn);
4326 }
4327 }
4328 }
4329 }
4330 }
4331 }
4332 }
4333
4334 void saveWorkspaceToDb() {
4335 saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
4336 int count = getChildCount();
4337 for (int i = 0; i < count; i++) {
4338 CellLayout cl = (CellLayout) getChildAt(i);
4339 saveWorkspaceScreenToDb(cl);
4340 }
4341 }
4342
4343 void saveWorkspaceScreenToDb(CellLayout cl) {
4344 int count = cl.getShortcutsAndWidgets().getChildCount();
4345
4346 long screenId = getIdForScreen(cl);
4347 int container = Favorites.CONTAINER_DESKTOP;
4348
4349 Hotseat hotseat = mLauncher.getHotseat();
4350 if (mLauncher.isHotseatLayout(cl)) {
4351 screenId = -1;
4352 container = Favorites.CONTAINER_HOTSEAT;
4353 }
4354
4355 for (int i = 0; i < count; i++) {
4356 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4357 ItemInfo info = (ItemInfo) v.getTag();
4358 // Null check required as the AllApps button doesn't have an item info
4359 if (info != null) {
4360 int cellX = info.cellX;
4361 int cellY = info.cellY;
4362 if (container == Favorites.CONTAINER_HOTSEAT) {
4363 cellX = hotseat.getCellXFromOrder((int) info.screenId);
4364 cellY = hotseat.getCellYFromOrder((int) info.screenId);
4365 }
4366 LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4367 cellY, false);
4368 }
4369 if (v instanceof FolderIcon) {
4370 FolderIcon fi = (FolderIcon) v;
4371 fi.getFolder().addItemLocationsInDatabase();
4372 }
4373 }
4374 }
4375
4376 @Override
4377 public float getIntrinsicIconScaleFactor() {
4378 return 1f;
4379 }
4380
4381 @Override
4382 public boolean supportsFlingToDelete() {
4383 return true;
4384 }
4385
4386 @Override
4387 public boolean supportsAppInfoDropTarget() {
4388 return false;
4389 }
4390
4391 @Override
4392 public boolean supportsDeleteDropTarget() {
4393 return true;
4394 }
4395
4396 @Override
4397 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4398 // Do nothing
4399 }
4400
4401 @Override
4402 public void onFlingToDeleteCompleted() {
4403 // Do nothing
4404 }
4405
4406 public boolean isDropEnabled() {
4407 return true;
4408 }
4409
4410 @Override
4411 protected void onRestoreInstanceState(Parcelable state) {
4412 super.onRestoreInstanceState(state);
4413 Launcher.setScreen(mCurrentPage);
4414 }
4415
4416 @Override
4417 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4418 // We don't dispatch restoreInstanceState to our children using this code path.
4419 // Some pages will be restored immediately as their items are bound immediately, and
4420 // others we will need to wait until after their items are bound.
4421 mSavedStates = container;
4422 }
4423
4424 public void restoreInstanceStateForChild(int child) {
4425 if (mSavedStates != null) {
4426 mRestoredPages.add(child);
4427 CellLayout cl = (CellLayout) getChildAt(child);
4428 if (cl != null) {
4429 cl.restoreInstanceState(mSavedStates);
4430 }
4431 }
4432 }
4433
4434 public void restoreInstanceStateForRemainingPages() {
4435 int count = getChildCount();
4436 for (int i = 0; i < count; i++) {
4437 if (!mRestoredPages.contains(i)) {
4438 restoreInstanceStateForChild(i);
4439 }
4440 }
4441 mRestoredPages.clear();
4442 mSavedStates = null;
4443 }
4444
4445 @Override
4446 public void scrollLeft() {
4447 if (!workspaceInModalState() && !mIsSwitchingState) {
4448 super.scrollLeft();
4449 }
4450 Folder openFolder = getOpenFolder();
4451 if (openFolder != null) {
4452 openFolder.completeDragExit();
4453 }
4454 }
4455
4456 @Override
4457 public void scrollRight() {
4458 if (!workspaceInModalState() && !mIsSwitchingState) {
4459 super.scrollRight();
4460 }
4461 Folder openFolder = getOpenFolder();
4462 if (openFolder != null) {
4463 openFolder.completeDragExit();
4464 }
4465 }
4466
4467 @Override
4468 public boolean onEnterScrollArea(int x, int y, int direction) {
4469 // Ignore the scroll area if we are dragging over the hot seat
4470 boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4471 if (mLauncher.getHotseat() != null && isPortrait) {
4472 Rect r = new Rect();
4473 mLauncher.getHotseat().getHitRect(r);
4474 if (r.contains(x, y)) {
4475 return false;
4476 }
4477 }
4478
4479 boolean result = false;
4480 if (!workspaceInModalState() && !mIsSwitchingState && getOpenFolder() == null) {
4481 mInScrollArea = true;
4482
4483 final int page = getNextPage() +
4484 (direction == DragController.SCROLL_LEFT ? -1 : 1);
4485 // We always want to exit the current layout to ensure parity of enter / exit
4486 setCurrentDropLayout(null);
4487
4488 if (0 <= page && page < getChildCount()) {
4489 // Ensure that we are not dragging over to the custom content screen
4490 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4491 return false;
4492 }
4493
4494 CellLayout layout = (CellLayout) getChildAt(page);
4495 setCurrentDragOverlappingLayout(layout);
4496
4497 // Workspace is responsible for drawing the edge glow on adjacent pages,
4498 // so we need to redraw the workspace when this may have changed.
4499 invalidate();
4500 result = true;
4501 }
4502 }
4503 return result;
4504 }
4505
4506 @Override
4507 public boolean onExitScrollArea() {
4508 boolean result = false;
4509 if (mInScrollArea) {
4510 invalidate();
4511 CellLayout layout = getCurrentDropLayout();
4512 setCurrentDropLayout(layout);
4513 setCurrentDragOverlappingLayout(layout);
4514
4515 result = true;
4516 mInScrollArea = false;
4517 }
4518 return result;
4519 }
4520
4521 private void onResetScrollArea() {
4522 setCurrentDragOverlappingLayout(null);
4523 mInScrollArea = false;
4524 }
4525
4526 /**
4527 * Returns a specific CellLayout
4528 */
4529 CellLayout getParentCellLayoutForView(View v) {
4530 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4531 for (CellLayout layout : layouts) {
4532 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4533 return layout;
4534 }
4535 }
4536 return null;
4537 }
4538
4539 /**
4540 * Returns a list of all the CellLayouts in the workspace.
4541 */
4542 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4543 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4544 int screenCount = getChildCount();
4545 for (int screen = 0; screen < screenCount; screen++) {
4546 layouts.add(((CellLayout) getChildAt(screen)));
4547 }
4548 if (mLauncher.getHotseat() != null) {
4549 layouts.add(mLauncher.getHotseat().getLayout());
4550 }
4551 return layouts;
4552 }
4553
4554 /**
4555 * We should only use this to search for specific children. Do not use this method to modify
4556 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4557 * the hotseat and workspace pages
4558 */
4559 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4560 ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4561 new ArrayList<ShortcutAndWidgetContainer>();
4562 int screenCount = getChildCount();
4563 for (int screen = 0; screen < screenCount; screen++) {
4564 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4565 }
4566 if (mLauncher.getHotseat() != null) {
4567 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4568 }
4569 return childrenLayouts;
4570 }
4571
4572 public Folder getFolderForTag(final Object tag) {
4573 return (Folder) getFirstMatch(new ItemOperator() {
4574
4575 @Override
4576 public boolean evaluate(ItemInfo info, View v, View parent) {
4577 return (v instanceof Folder) && (((Folder) v).getInfo() == tag)
4578 && ((Folder) v).getInfo().opened;
4579 }
4580 });
4581 }
4582
4583 public View getViewForTag(final Object tag) {
4584 return getFirstMatch(new ItemOperator() {
4585
4586 @Override
4587 public boolean evaluate(ItemInfo info, View v, View parent) {
4588 return info == tag;
4589 }
4590 });
4591 }
4592
4593 public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
4594 return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
4595
4596 @Override
4597 public boolean evaluate(ItemInfo info, View v, View parent) {
4598 return (info instanceof LauncherAppWidgetInfo) &&
4599 ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
4600 }
4601 });
4602 }
4603
4604 private View getFirstMatch(final ItemOperator operator) {
4605 final View[] value = new View[1];
4606 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4607 @Override
4608 public boolean evaluate(ItemInfo info, View v, View parent) {
4609 if (operator.evaluate(info, v, parent)) {
4610 value[0] = v;
4611 return true;
4612 }
4613 return false;
4614 }
4615 });
4616 return value[0];
4617 }
4618
4619 void clearDropTargets() {
4620 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4621 @Override
4622 public boolean evaluate(ItemInfo info, View v, View parent) {
4623 if (v instanceof DropTarget) {
4624 mDragController.removeDropTarget((DropTarget) v);
4625 }
4626 // not done, process all the shortcuts
4627 return false;
4628 }
4629 });
4630 }
4631
4632 // Removes ALL items that match a given package name, this is usually called when a package
4633 // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4634 // belong to that package.
4635 void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
4636 final HashSet<String> packageNames = new HashSet<String>();
4637 packageNames.addAll(packages);
4638
4639 // Filter out all the ItemInfos that this is going to affect
4640 final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4641 final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4642 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4643 for (CellLayout layoutParent : cellLayouts) {
4644 ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4645 int childCount = layout.getChildCount();
4646 for (int i = 0; i < childCount; ++i) {
4647 View view = layout.getChildAt(i);
4648 infos.add((ItemInfo) view.getTag());
4649 }
4650 }
4651 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4652 @Override
4653 public boolean filterItem(ItemInfo parent, ItemInfo info,
4654 ComponentName cn) {
4655 if (packageNames.contains(cn.getPackageName())
4656 && info.user.equals(user)) {
4657 cns.add(cn);
4658 return true;
4659 }
4660 return false;
4661 }
4662 };
4663 LauncherModel.filterItemInfos(infos, filter);
4664
4665 // Remove the affected components
4666 removeItemsByComponentName(cns, user);
4667 }
4668
4669 // Removes items that match the application info specified, when applications are removed
4670 // as a part of an update, this is called to ensure that other widgets and application
4671 // shortcuts are not removed.
4672 void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos, UserHandleCompat user) {
4673 // Just create a hash table of all the specific components that this will affect
4674 HashSet<ComponentName> cns = new HashSet<ComponentName>();
4675 for (AppInfo info : appInfos) {
4676 cns.add(info.componentName);
4677 }
4678
4679 // Remove all the things
4680 removeItemsByComponentName(cns, user);
4681 }
4682
4683 void removeItemsByComponentName(final HashSet<ComponentName> componentNames,
4684 final UserHandleCompat user) {
4685 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4686 for (final CellLayout layoutParent: cellLayouts) {
4687 final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4688
4689 final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4690 for (int j = 0; j < layout.getChildCount(); j++) {
4691 final View view = layout.getChildAt(j);
4692 children.put((ItemInfo) view.getTag(), view);
4693 }
4694
4695 final ArrayList<View> childrenToRemove = new ArrayList<View>();
4696 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove =
4697 new HashMap<FolderInfo, ArrayList<ShortcutInfo>>();
4698 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4699 @Override
4700 public boolean filterItem(ItemInfo parent, ItemInfo info,
4701 ComponentName cn) {
4702 if (parent instanceof FolderInfo) {
4703 if (componentNames.contains(cn) && info.user.equals(user)) {
4704 FolderInfo folder = (FolderInfo) parent;
4705 ArrayList<ShortcutInfo> appsToRemove;
4706 if (folderAppsToRemove.containsKey(folder)) {
4707 appsToRemove = folderAppsToRemove.get(folder);
4708 } else {
4709 appsToRemove = new ArrayList<ShortcutInfo>();
4710 folderAppsToRemove.put(folder, appsToRemove);
4711 }
4712 appsToRemove.add((ShortcutInfo) info);
4713 return true;
4714 }
4715 } else {
4716 if (componentNames.contains(cn) && info.user.equals(user)) {
4717 childrenToRemove.add(children.get(info));
4718 return true;
4719 }
4720 }
4721 return false;
4722 }
4723 };
4724 LauncherModel.filterItemInfos(children.keySet(), filter);
4725
4726 // Remove all the apps from their folders
4727 for (FolderInfo folder : folderAppsToRemove.keySet()) {
4728 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4729 for (ShortcutInfo info : appsToRemove) {
4730 folder.remove(info);
4731 }
4732 }
4733
4734 // Remove all the other children
4735 for (View child : childrenToRemove) {
4736 // Note: We can not remove the view directly from CellLayoutChildren as this
4737 // does not re-mark the spaces as unoccupied.
4738 layoutParent.removeViewInLayout(child);
4739 if (child instanceof DropTarget) {
4740 mDragController.removeDropTarget((DropTarget) child);
4741 }
4742 }
4743
4744 if (childrenToRemove.size() > 0) {
4745 layout.requestLayout();
4746 layout.invalidate();
4747 }
4748 }
4749
4750 // Strip all the empty screens
4751 stripEmptyScreens();
4752 }
4753
4754 interface ItemOperator {
4755 /**
4756 * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
4757 *
4758 * @param info info for the shortcut
4759 * @param view view for the shortcut
4760 * @param parent containing folder, or null
4761 * @return true if done, false to continue the map
4762 */
4763 public boolean evaluate(ItemInfo info, View view, View parent);
4764 }
4765
4766 /**
4767 * Map the operator over the shortcuts and widgets, return the first-non-null value.
4768 *
4769 * @param recurse true: iterate over folder children. false: op get the folders themselves.
4770 * @param op the operator to map over the shortcuts
4771 */
4772 void mapOverItems(boolean recurse, ItemOperator op) {
4773 ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
4774 final int containerCount = containers.size();
4775 for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
4776 ShortcutAndWidgetContainer container = containers.get(containerIdx);
4777 // map over all the shortcuts on the workspace
4778 final int itemCount = container.getChildCount();
4779 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
4780 View item = container.getChildAt(itemIdx);
4781 ItemInfo info = (ItemInfo) item.getTag();
4782 if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
4783 FolderIcon folder = (FolderIcon) item;
4784 ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4785 // map over all the children in the folder
4786 final int childCount = folderChildren.size();
4787 for (int childIdx = 0; childIdx < childCount; childIdx++) {
4788 View child = folderChildren.get(childIdx);
4789 info = (ItemInfo) child.getTag();
4790 if (op.evaluate(info, child, folder)) {
4791 return;
4792 }
4793 }
4794 } else {
4795 if (op.evaluate(info, item, null)) {
4796 return;
4797 }
4798 }
4799 }
4800 }
4801 }
4802
4803 void updateShortcutsAndWidgets(ArrayList<AppInfo> apps) {
4804 // Break the appinfo list per user
4805 final HashMap<UserHandleCompat, ArrayList<AppInfo>> appsPerUser =
4806 new HashMap<UserHandleCompat, ArrayList<AppInfo>>();
4807 for (AppInfo info : apps) {
4808 ArrayList<AppInfo> filtered = appsPerUser.get(info.user);
4809 if (filtered == null) {
4810 filtered = new ArrayList<AppInfo>();
4811 appsPerUser.put(info.user, filtered);
4812 }
4813 filtered.add(info);
4814 }
4815
4816 for (Map.Entry<UserHandleCompat, ArrayList<AppInfo>> entry : appsPerUser.entrySet()) {
4817 updateShortcutsAndWidgetsPerUser(entry.getValue(), entry.getKey());
4818 }
4819 }
4820
4821 private void updateShortcutsAndWidgetsPerUser(ArrayList<AppInfo> apps,
4822 final UserHandleCompat user) {
4823 // Create a map of the apps to test against
4824 final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
4825 final HashSet<String> pkgNames = new HashSet<String>();
4826 for (AppInfo ai : apps) {
4827 appsMap.put(ai.componentName, ai);
4828 pkgNames.add(ai.componentName.getPackageName());
4829 }
4830 final HashSet<ComponentName> iconsToRemove = new HashSet<ComponentName>();
4831
4832 mapOverItems(MAP_RECURSE, new ItemOperator() {
4833 @Override
4834 public boolean evaluate(ItemInfo info, View v, View parent) {
4835 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4836 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4837 ComponentName cn = shortcutInfo.getTargetComponent();
4838 AppInfo appInfo = appsMap.get(cn);
4839 if (user.equals(shortcutInfo.user) && cn != null
4840 && LauncherModel.isShortcutInfoUpdateable(info)
4841 && pkgNames.contains(cn.getPackageName())) {
4842 boolean promiseStateChanged = false;
4843 boolean infoUpdated = false;
4844 if (shortcutInfo.isPromise()) {
4845 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4846 // Auto install icon
4847 PackageManager pm = getContext().getPackageManager();
4848 ResolveInfo matched = pm.resolveActivity(
4849 new Intent(Intent.ACTION_MAIN)
4850 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
4851 PackageManager.MATCH_DEFAULT_ONLY);
4852 if (matched == null) {
4853 // Try to find the best match activity.
4854 Intent intent = pm.getLaunchIntentForPackage(
4855 cn.getPackageName());
4856 if (intent != null) {
4857 cn = intent.getComponent();
4858 appInfo = appsMap.get(cn);
4859 }
4860
4861 if ((intent == null) || (appsMap == null)) {
4862 // Could not find a default activity. Remove this item.
4863 iconsToRemove.add(shortcutInfo.getTargetComponent());
4864
4865 // process next shortcut.
4866 return false;
4867 }
4868 shortcutInfo.promisedIntent = intent;
4869 }
4870 }
4871
4872 // Restore the shortcut.
4873 shortcutInfo.intent = shortcutInfo.promisedIntent;
4874 shortcutInfo.promisedIntent = null;
4875 shortcutInfo.status &= ~ShortcutInfo.FLAG_RESTORED_ICON
4876 & ~ShortcutInfo.FLAG_AUTOINTALL_ICON
4877 & ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
4878
4879 promiseStateChanged = true;
4880 infoUpdated = true;
4881 shortcutInfo.updateIcon(mIconCache);
4882 LauncherModel.updateItemInDatabase(getContext(), shortcutInfo);
4883 }
4884
4885
4886 if (appInfo != null) {
4887 shortcutInfo.updateIcon(mIconCache);
4888 shortcutInfo.title = appInfo.title.toString();
4889 shortcutInfo.contentDescription = appInfo.contentDescription;
4890 infoUpdated = true;
4891 }
4892
4893 if (infoUpdated) {
4894 BubbleTextView shortcut = (BubbleTextView) v;
4895 shortcut.applyFromShortcutInfo(shortcutInfo,
4896 mIconCache, true, promiseStateChanged);
4897
4898 if (parent != null) {
4899 parent.invalidate();
4900 }
4901 }
4902 }
4903 }
4904 // process all the shortcuts
4905 return false;
4906 }
4907 });
4908
4909 if (!iconsToRemove.isEmpty()) {
4910 removeItemsByComponentName(iconsToRemove, user);
4911 }
4912 if (user.equals(UserHandleCompat.myUserHandle())) {
4913 restorePendingWidgets(pkgNames);
4914 }
4915 }
4916
4917 public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
4918 ArrayList<String> packages = new ArrayList<String>(1);
4919 packages.add(packageName);
4920 LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
4921 removeItemsByPackageName(packages, user);
4922 }
4923
4924 public void updatePackageBadge(final String packageName, final UserHandleCompat user) {
4925 mapOverItems(MAP_RECURSE, new ItemOperator() {
4926 @Override
4927 public boolean evaluate(ItemInfo info, View v, View parent) {
4928 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4929 ShortcutInfo shortcutInfo = (ShortcutInfo) info;
4930 ComponentName cn = shortcutInfo.getTargetComponent();
4931 if (user.equals(shortcutInfo.user) && cn != null
4932 && shortcutInfo.isPromise()
4933 && packageName.equals(cn.getPackageName())) {
4934 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4935 // For auto install apps update the icon as well as label.
4936 mIconCache.getTitleAndIcon(shortcutInfo,
4937 shortcutInfo.promisedIntent, user, true);
4938 } else {
4939 // Only update the icon for restored apps.
4940 shortcutInfo.updateIcon(mIconCache);
4941 }
4942 BubbleTextView shortcut = (BubbleTextView) v;
4943 shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
4944
4945 if (parent != null) {
4946 parent.invalidate();
4947 }
4948 }
4949 }
4950 // process all the shortcuts
4951 return false;
4952 }
4953 });
4954 }
4955
4956 public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {
4957 HashSet<String> completedPackages = new HashSet<String>();
4958
4959 for (final PackageInstallInfo installInfo : installInfos) {
4960 mapOverItems(MAP_RECURSE, new ItemOperator() {
4961 @Override
4962 public boolean evaluate(ItemInfo info, View v, View parent) {
4963 if (info instanceof ShortcutInfo && v instanceof BubbleTextView) {
4964 ShortcutInfo si = (ShortcutInfo) info;
4965 ComponentName cn = si.getTargetComponent();
4966 if (si.isPromise() && (cn != null)
4967 && installInfo.packageName.equals(cn.getPackageName())) {
4968 si.setInstallProgress(installInfo.progress);
4969 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
4970 // Mark this info as broken.
4971 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
4972 }
4973 ((BubbleTextView)v).applyState(false);
4974 }
4975 } else if (v instanceof PendingAppWidgetHostView
4976 && info instanceof LauncherAppWidgetInfo
4977 && ((LauncherAppWidgetInfo) info).providerName.getPackageName()
4978 .equals(installInfo.packageName)) {
4979 ((LauncherAppWidgetInfo) info).installProgress = installInfo.progress;
4980 ((PendingAppWidgetHostView) v).applyState();
4981 }
4982
4983 // process all the shortcuts
4984 return false;
4985 }
4986 });
4987
4988 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
4989 completedPackages.add(installInfo.packageName);
4990 }
4991 }
4992
4993 // Note that package states are sent only for myUser
4994 if (!completedPackages.isEmpty()) {
4995 restorePendingWidgets(completedPackages);
4996 }
4997 }
4998
4999 private void restorePendingWidgets(final Set<String> installedPackaged) {
5000 final ArrayList<LauncherAppWidgetInfo> changedInfo = new ArrayList<LauncherAppWidgetInfo>();
5001
5002 // Iterate non recursively as widgets can't be inside a folder.
5003 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
5004
5005 @Override
5006 public boolean evaluate(ItemInfo info, View v, View parent) {
5007 if (info instanceof LauncherAppWidgetInfo) {
5008 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
5009 if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
5010 && installedPackaged.contains(widgetInfo.providerName.getPackageName())) {
5011
5012 changedInfo.add(widgetInfo);
5013
5014 // Remove the provider not ready flag
5015 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
5016 LauncherModel.updateItemInDatabase(getContext(), widgetInfo);
5017 }
5018 }
5019 // process all the widget
5020 return false;
5021 }
5022 });
5023 if (!changedInfo.isEmpty()) {
5024 DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
5025 mLauncher.getAppWidgetHost());
5026 if (LauncherModel.findAppWidgetProviderInfoWithComponent(getContext(),
5027 changedInfo.get(0).providerName) != null) {
5028 // Re-inflate the widgets which have changed status
5029 widgetRefresh.run();
5030 } else {
5031 // widgetRefresh will automatically run when the packages are updated.
5032 }
5033 }
5034 }
5035
5036 private void moveToScreen(int page, boolean animate) {
5037 if (!workspaceInModalState()) {
5038 if (animate) {
5039 snapToPage(page);
5040 } else {
5041 setCurrentPage(page);
5042 }
5043 }
5044 View child = getChildAt(page);
5045 if (child != null) {
5046 child.requestFocus();
5047 }
5048 }
5049
5050 void moveToDefaultScreen(boolean animate) {
5051 moveToScreen(mDefaultPage, animate);
5052 }
5053
5054 void moveToCustomContentScreen(boolean animate) {
5055 if (hasCustomContent()) {
5056 int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
5057 if (animate) {
5058 snapToPage(ccIndex);
5059 } else {
5060 setCurrentPage(ccIndex);
5061 }
5062 View child = getChildAt(ccIndex);
5063 if (child != null) {
5064 child.requestFocus();
5065 }
5066 }
5067 exitWidgetResizeMode();
5068 }
5069
5070 @Override
5071 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
5072 long screenId = getScreenIdForPageIndex(pageIndex);
5073 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
5074 int count = mScreenOrder.size() - numCustomPages();
5075 if (count > 1) {
5076 return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
5077 R.drawable.ic_pageindicator_add);
5078 }
5079 }
5080
5081 return super.getPageIndicatorMarker(pageIndex);
5082 }
5083
5084 @Override
5085 public void syncPages() {
5086 }
5087
5088 @Override
5089 public void syncPageItems(int page, boolean immediate) {
5090 }
5091
5092 protected String getPageIndicatorDescription() {
5093 String settings = getResources().getString(R.string.settings_button_text);
5094 return getCurrentPageDescription() + ", " + settings;
5095 }
5096
5097 protected String getCurrentPageDescription() {
5098 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
5099 int delta = numCustomPages();
5100 if (hasCustomContent() && getNextPage() == 0) {
5101 return mCustomContentDescription;
5102 }
5103 return String.format(getContext().getString(R.string.workspace_scroll_format),
5104 page + 1 - delta, getChildCount() - delta);
5105 }
5106
5107 public void getLocationInDragLayer(int[] loc) {
5108 mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
5109 }
5110
5111 /**
5112 * Used as a workaround to ensure that the AppWidgetService receives the
5113 * PACKAGE_ADDED broadcast before updating widgets.
5114 */
5115 private class DeferredWidgetRefresh implements Runnable {
5116 private final ArrayList<LauncherAppWidgetInfo> mInfos;
5117 private final LauncherAppWidgetHost mHost;
5118 private final Handler mHandler;
5119
5120 private boolean mRefreshPending;
5121
5122 public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
5123 LauncherAppWidgetHost host) {
5124 mInfos = infos;
5125 mHost = host;
5126 mHandler = new Handler();
5127 mRefreshPending = true;
5128
5129 mHost.addProviderChangeListener(this);
5130 // Force refresh after 10 seconds, if we don't get the provider changed event.
5131 // This could happen when the provider is no longer available in the app.
5132 mHandler.postDelayed(this, 10000);
5133 }
5134
5135 @Override
5136 public void run() {
5137 mHost.removeProviderChangeListener(this);
5138 mHandler.removeCallbacks(this);
5139
5140 if (!mRefreshPending) {
5141 return;
5142 }
5143
5144 mRefreshPending = false;
5145
5146 for (LauncherAppWidgetInfo info : mInfos) {
5147 if (info.hostView instanceof PendingAppWidgetHostView) {
5148 PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
5149 mLauncher.removeAppWidget(info);
5150
5151 CellLayout cl = (CellLayout) view.getParent().getParent();
5152 // Remove the current widget
5153 cl.removeView(view);
5154 mLauncher.bindAppWidget(info);
5155 }
5156 }
5157 }
5158 }
5159 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.animation.Animator.AnimatorListener;
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.LayoutTransition;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.animation.TimeInterpolator;
26 import android.animation.ValueAnimator.AnimatorUpdateListener;
27 import android.animation.ValueAnimator;
28 import android.app.WallpaperManager;
29 import android.appwidget.AppWidgetHostView;
30 import android.appwidget.AppWidgetProviderInfo;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.SharedPreferences;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ResolveInfo;
37 import android.content.res.Resources;
38 import android.content.res.TypedArray;
39 import android.graphics.Bitmap;
40 import android.graphics.Canvas;
41 import android.graphics.Matrix;
42 import android.graphics.Paint;
43 import android.graphics.Point;
44 import android.graphics.PointF;
45 import android.graphics.Rect;
46 import android.graphics.Region.Op;
47 import android.graphics.drawable.Drawable;
48 import android.net.Uri;
49 import android.os.AsyncTask;
50 import android.os.Handler;
51 import android.os.IBinder;
52 import android.os.Parcelable;
53 import android.support.v4.view.ViewCompat;
54 import android.util.AttributeSet;
55 import android.util.Log;
56 import android.util.SparseArray;
57 import android.view.Choreographer;
58 import android.view.Display;
59 import android.view.MotionEvent;
60 import android.view.View;
61 import android.view.ViewGroup;
62 import android.view.accessibility.AccessibilityManager;
63 import android.view.animation.DecelerateInterpolator;
64 import android.view.animation.Interpolator;
65 import android.widget.TextView;
66 import com.android.launcher3.FolderIcon.FolderRingAnimator;
67 import com.android.launcher3.Launcher.CustomContentCallbacks;
68 import com.android.launcher3.LauncherSettings.Favorites;
69 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
70 import com.android.launcher3.compat.PackageInstallerCompat;
71 import com.android.launcher3.compat.UserHandleCompat;
72 import java.util.ArrayList;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.Map;
77 import java.util.Set;
78 import java.util.concurrent.atomic.AtomicInteger;
79
80
81 /**
82 * The workspace is a wide area with a wallpaper and a finite number of pages.
83 * Each page contains a number of icons, folders or widgets the user can
84 * interact with. A workspace is meant to be used with a fixed width only.
85 */
86 public class Workspace extends SmoothPagedView implements DropTarget , DragSource , DragScroller , View.O🔵
87 private static final String TAG = "Launcher.Workspace";
88
89 // Y rotation to apply to the workspace screens
90 // Y rotation to apply to the workspace screens
91 private static final float WORKSPACE_OVERSCROLL_ROTATION = 24f;
92
93 private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
94
95 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375;
96
97 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100;
98
99 protected static final int SNAP_OFF_EMPTY_SCREEN_DURATION = 400;
100
101 protected static final int FADE_EMPTY_SCREEN_DURATION = 150;
102
103 private static final int BACKGROUND_FADE_OUT_DURATION = 350;
104
105 private static final int ADJACENT_SCREEN_DROP_DURATION = 300;
106
107 private static final int FLING_THRESHOLD_VELOCITY = 500;
108
109 private static final float ALPHA_CUTOFF_THRESHOLD = 0.01f;
110
111 static final boolean MAP_NO_RECURSE = false;
112
113 static final boolean MAP_RECURSE = true;
114
115 // These animators are used to fade the children's outlines
116 // These animators are used to fade the children's outlines
117 private ObjectAnimator mChildrenOutlineFadeInAnimation;
118
119 private ObjectAnimator mChildrenOutlineFadeOutAnimation;
120
121 private float mChildrenOutlineAlpha = 0;
122
123 // These properties refer to the background protection gradient used for AllApps and Customize
124 // These properties refer to the background protection gradient used for AllApps and Customize
125 private ValueAnimator mBackgroundFadeInAnimation;
126
127 private ValueAnimator mBackgroundFadeOutAnimation;
128
129 private static final long CUSTOM_CONTENT_GESTURE_DELAY = 200;
130
131 private long mTouchDownTime = -1;
132
133 private long mCustomContentShowTime = -1;
134
135 private LayoutTransition mLayoutTransition;
136
137 private final WallpaperManager mWallpaperManager;
138
139 private IBinder mWindowToken;
140
141 private int mOriginalDefaultPage;
142
143 private int mDefaultPage;
144
145 private ShortcutAndWidgetContainer mDragSourceInternal;
146
147 private static boolean sAccessibilityEnabled;
148
149 // The screen id used for the empty screen always present to the right.
150 static final long EXTRA_EMPTY_SCREEN_ID = -201;
151
152 private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
153
154 private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
155
156 private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
157
158 private Runnable mRemoveEmptyScreenRunnable;
159
160 private boolean mDeferRemoveExtraEmptyScreen = false;
161
162 /**
163 * CellInfo for the cell that is currently being dragged
164 */
165 private CellLayout.CellInfo mDragInfo;
166
167 /**
168 * Target drop area calculated during last acceptDrop call.
169 */
170 private int[] mTargetCell = new int[2];
171
172 private int mDragOverX = -1;
173
174 private int mDragOverY = -1;
175
176 static Rect mLandscapeCellLayoutMetrics = null;
177
178 static Rect mPortraitCellLayoutMetrics = null;
179
180 CustomContentCallbacks mCustomContentCallbacks;
181
182 boolean mCustomContentShowing;
183
184 private float mLastCustomContentScrollProgress = -1f;
185
186 private String mCustomContentDescription = "";
187
188 /**
189 * The CellLayout that is currently being dragged over
190 */
191 private CellLayout mDragTargetLayout = null;
192
193 /**
194 * The CellLayout that we will show as glowing
195 */
196 private CellLayout mDragOverlappingLayout = null;
197
198 /**
199 * The CellLayout which will be dropped to
200 */
201 private CellLayout mDropToLayout = null;
202
203 private Launcher mLauncher;
204
205 private IconCache mIconCache;
206
207 private DragController mDragController;
208
209 // These are temporary variables to prevent having to allocate a new object just to
210 // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
211 private int[] mTempCell = new int[2];
212
213 private int[] mTempPt = new int[2];
214
215 private int[] mTempEstimate = new int[2];
216
217 private float[] mDragViewVisualCenter = new float[2];
218
219 private float[] mTempCellLayoutCenterCoordinates = new float[2];
220
221 private Matrix mTempInverseMatrix = new Matrix();
222
223 private SpringLoadedDragController mSpringLoadedDragController;
224
225 private float mSpringLoadedShrinkFactor;
226
227 private float mOverviewModeShrinkFactor;
228
229 // State variable that indicates whether the pages are small (ie when you're
230 // in all apps or customize mode)
231 enum State {
232
233 NORMAL,
234 NORMAL_HIDDEN,
235 SPRING_LOADED,
236 OVERVIEW,
237 OVERVIEW_HIDDEN;}
238
239 private State mState = State.NORMAL;
240
241 private boolean mIsSwitchingState = false;
242
243 boolean mAnimatingViewIntoPlace = false;
244
245 boolean mIsDragOccuring = false;
246
247 boolean mChildrenLayersEnabled = true;
248
249 private boolean mStripScreensOnPageStopMoving = false;
250
251 /** Is the user is dragging an item near the edge of a page? */
252 private boolean mInScrollArea = false;
253
254 private HolographicOutlineHelper mOutlineHelper;
255
256 private Bitmap mDragOutline = null;
257
258 private static final Rect sTempRect = new Rect();
259
260 private final int[] mTempXY = new int[2];
261
262 private int[] mTempVisiblePagesRange = new int[2];
263
264 private boolean mOverscrollEffectSet;
265
266 public static final int DRAG_BITMAP_PADDING = 2;
267
268 private boolean mWorkspaceFadeInAdjacentScreens;
269
270 WallpaperOffsetInterpolator mWallpaperOffset;
271
272 private boolean mWallpaperIsLiveWallpaper;
273
274 private int mNumPagesForWallpaperParallax;
275
276 private float mLastSetWallpaperOffsetSteps = 0;
277
278 private Runnable mDelayedResizeRunnable;
279
280 private Runnable mDelayedSnapToPageRunnable;
281
282 private Point mDisplaySize = new Point();
283
284 private int mCameraDistance;
285
286 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
287 // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
288 private static final int FOLDER_CREATION_TIMEOUT = 0;
289
290 public static final int REORDER_TIMEOUT = 350;
291
292 private final Alarm mFolderCreationAlarm = new Alarm();
293
294 private final Alarm mReorderAlarm = new Alarm();
295
296 private FolderRingAnimator mDragFolderRingAnimator = null;
297
298 private FolderIcon mDragOverFolderIcon = null;
299
300 private boolean mCreateUserFolderOnDrop = false;
301
302 private boolean mAddToExistingFolderOnDrop = false;
303
304 private DropTarget.DragEnforcer mDragEnforcer;
305
306 private float mMaxDistanceForFolderCreation;
307
308 private final Canvas mCanvas = new Canvas();
309
310 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
311 // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
312 private float mXDown;
313
314 private float mYDown;
315
316 final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
317
318 final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
319
320 final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
321
322 // Relating to the animation of items being dropped externally
323 // Relating to the animation of items being dropped externally
324 public static final int ANIMATE_INTO_POSITION_AND_DISAPPEAR = 0;
325
326 public static final int ANIMATE_INTO_POSITION_AND_REMAIN = 1;
327
328 public static final int ANIMATE_INTO_POSITION_AND_RESIZE = 2;
329
330 public static final int COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION = 3;
331
332 public static final int CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION = 4;
333
334 // Related to dragging, folder creation and reordering
335 private static final int DRAG_MODE_NONE = 0;
336
337 private static final int DRAG_MODE_CREATE_FOLDER = 1;
338
339 private static final int DRAG_MODE_ADD_TO_FOLDER = 2;
340
341 private static final int DRAG_MODE_REORDER = 3;
342
343 private int mDragMode = DRAG_MODE_NONE;
344
345 private int mLastReorderX = -1;
346
347 private int mLastReorderY = -1;
348
349 private SparseArray<Parcelable> mSavedStates;
350
351 private final ArrayList<Integer> mRestoredPages = new ArrayList<Integer>();
352
353 // These variables are used for storing the initial and final values during workspace animations
354 // These variables are used for storing the initial and final values during workspace animations
355 private int mSavedScrollX;
356
357 private float mSavedRotationY;
358
359 private float mSavedTranslationX;
360
361 private float mCurrentScale;
362
363 private float mNewScale;
364
365 private float[] mOldBackgroundAlphas;
366
367 private float[] mOldAlphas;
368
369 private float[] mNewBackgroundAlphas;
370
371 private float[] mNewAlphas;
372
373 private int mLastChildCount = -1;
374
375 private float mTransitionProgress;
376
377 float mOverScrollEffect = 0f;
378
379 private Runnable mDeferredAction;
380
381 private boolean mDeferDropAfterUninstall;
382
383 private boolean mUninstallSuccessful;
384
385 private final Runnable mBindPages = new Runnable() {
386 @Override
387 public void run() {
388 mLauncher.getModel().bindRemainingSynchronousPages();
389 }
390 };
391
392 /**
393 * Used to inflate the Workspace from XML.
394 *
395 * @param context
396 * The application's context.
397 * @param attrs
398 * The attributes set containing the Workspace's customization values.
399 */
400 public Workspace(Context context, AttributeSet attrs) {
401 this(context, attrs, 0);
402 }
403
404 /**
405 * Used to inflate the Workspace from XML.
406 *
407 * @param context
408 * The application's context.
409 * @param attrs
410 * The attributes set containing the Workspace's customization values.
411 * @param defStyle
412 * Unused.
413 */
414 public Workspace(Context context, AttributeSet attrs, int defStyle) {
415 super(context, attrs, defStyle);
416 mContentIsRefreshable = false;
417 mOutlineHelper = HolographicOutlineHelper.obtain(context);
418 mDragEnforcer = new DropTarget.DragEnforcer(context);
419 // With workspace, data is available straight from the get-go
420 setDataIsReady();
421 mLauncher = ((Launcher) (context));
422 final Resources res = getResources();
423 mWorkspaceFadeInAdjacentScreens = LauncherAppState.getInstance().getDynamicGrid().getDeviceProfil🔵
424 mFadeInAdjacentScreens = false;
425 mWallpaperManager = WallpaperManager.getInstance(context);
426 LauncherAppState app = LauncherAppState.getInstance();
427 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
428 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
429 mSpringLoadedShrinkFactor = res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) 🔵
430 mOverviewModeShrinkFactor = grid.getOverviewModeScale();
431 mCameraDistance = res.getInteger(R.integer.config_cameraDistance);
432 mOriginalDefaultPage = mDefaultPage = a.getInt(R.styleable.Workspace_defaultScreen, 1);
433 a.recycle();
434 setOnHierarchyChangeListener(this);
435 setHapticFeedbackEnabled(false);
436 initWorkspace();
437 // Disable multitouch across the workspace/all apps/customize tray
438 setMotionEventSplittingEnabled(true);
439 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
440 }
441
442 @Override
443 public void setInsets(Rect insets) {
444 mInsets.set(insets);
445
446 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
447 if (customScreen != null) {
448 View customContent = customScreen.getShortcutsAndWidgets().getChildAt(0);
449 if (customContent instanceof Insettable) {
450 ((Insettable) customContent).setInsets(mInsets);
451 }
452 }
453 }
454
455 // estimate the size of a widget with spans hSpan, vSpan. return MAX_VALUE for each
456 // dimension if unsuccessful
457 public int[] estimateItemSize(int hSpan, int vSpan,
458 ItemInfo itemInfo, boolean springLoaded) {
459 int[] size = new int[2];
460 if (getChildCount() > 0) {
461 // Use the first non-custom page to estimate the child position
462 CellLayout cl = (CellLayout) getChildAt(numCustomPages());
463 Rect r = estimateItemPosition(cl, itemInfo, 0, 0, hSpan, vSpan);
464 size[0] = r.width();
465 size[1] = r.height();
466 if (springLoaded) {
467 size[0] *= mSpringLoadedShrinkFactor;
468 size[1] *= mSpringLoadedShrinkFactor;
469 }
470 return size;
471 } else {
472 size[0] = Integer.MAX_VALUE;
473 size[1] = Integer.MAX_VALUE;
474 return size;
475 }
476 }
477
478 public Rect estimateItemPosition(CellLayout cl, ItemInfo pendingInfo,
479 int hCell, int vCell, int hSpan, int vSpan) {
480 Rect r = new Rect();
481 cl.cellToRect(hCell, vCell, hSpan, vSpan, r);
482 return r;
483 }
484
485 public void onDragStart(final DragSource source, Object info, int dragAction) {
486 mIsDragOccuring = true;
487 updateChildrenLayersEnabled(false);
488 mLauncher.lockScreenOrientation();
489 mLauncher.onInteractionBegin();
490 setChildrenBackgroundAlphaMultipliers(1.0F);
491 // Prevent any Un/InstallShortcutReceivers from updating the db while we are dragging
492 InstallShortcutReceiver.enableInstallQueue();
493 UninstallShortcutReceiver.enableUninstallQueue();
494 post(new Runnable() {
495 @Override
496 public void run() {
497 if (mIsDragOccuring) {
498 mDeferRemoveExtraEmptyScreen = false;
499 addExtraEmptyScreenOnDrag();
500 }
501 }
502 });
503 }
504
505 public void deferRemoveExtraEmptyScreen() {
506 mDeferRemoveExtraEmptyScreen = true;
507 }
508
509 public void onDragEnd() {
510 if (!mDeferRemoveExtraEmptyScreen) {
511 removeExtraEmptyScreen(true, mDragSourceInternal != null);
512 }
513 mIsDragOccuring = false;
514 updateChildrenLayersEnabled(false);
515 mLauncher.unlockScreenOrientation(false);
516 // Re-enable any Un/InstallShortcutReceiver and now process any queued items
517 InstallShortcutReceiver.disableAndFlushInstallQueue(getContext());
518 UninstallShortcutReceiver.disableAndFlushUninstallQueue(getContext());
519 mDragSourceInternal = null;
520 mLauncher.onInteractionEnd();
521 }
522
523 /**
524 * Initializes various states for this workspace.
525 */
526 protected void initWorkspace() {
527 mCurrentPage = mDefaultPage;
528 Launcher.setScreen(mCurrentPage);
529 LauncherAppState app = LauncherAppState.getInstance();
530 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
531 mIconCache = app.getIconCache();
532 setWillNotDraw(false);
533 setClipChildren(false);
534 setClipToPadding(false);
535 setChildrenDrawnWithCacheEnabled(true);
536 setMinScale(mOverviewModeShrinkFactor);
537 setupLayoutTransition();
538 mWallpaperOffset = new WallpaperOffsetInterpolator();
539 Display display = mLauncher.getWindowManager().getDefaultDisplay();
540 display.getSize(mDisplaySize);
541 mMaxDistanceForFolderCreation = 0.55F * grid.iconSizePx;
542 mFlingThresholdVelocity = ((int) (FLING_THRESHOLD_VELOCITY * mDensity));
543 // Set the wallpaper dimensions when Launcher starts up
544 setWallpaperDimension();
545 }
546
547 private void setupLayoutTransition() {
548 // We want to show layout transitions when pages are deleted, to close the gap.
549 mLayoutTransition = new LayoutTransition();
550 mLayoutTransition.enableTransitionType(LayoutTransition.DISAPPEARING);
551 mLayoutTransition.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
552 mLayoutTransition.disableTransitionType(LayoutTransition.APPEARING);
553 mLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
554 setLayoutTransition(mLayoutTransition);
555 }
556
557 void enableLayoutTransitions() {
558 setLayoutTransition(mLayoutTransition);
559 }
560
561 void disableLayoutTransitions() {
562 setLayoutTransition(null);
563 }
564
565 @Override
566 protected int getScrollMode() {
567 return SmoothPagedView.X_LARGE_MODE;
568 }
569
570 @Override
571 public void onChildViewAdded(View parent, View child) {
572 if (!(child instanceof CellLayout)) {
573 throw new IllegalArgumentException("A Workspace can only have CellLayout children.");
574 }
575 CellLayout cl = ((CellLayout) child);
576 cl.setOnInterceptTouchListener(this);
577 cl.setClickable(true);
578 cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
579 super.onChildViewAdded(parent, child);
580 }
581
582 protected boolean shouldDrawChild(View child) {
583 final CellLayout cl = (CellLayout) child;
584 return super.shouldDrawChild(child) &&
585 (mIsSwitchingState ||
586 cl.getShortcutsAndWidgets().getAlpha() > 0 ||
587 cl.getBackgroundAlpha() > 0);
588 }
589
590 /**
591 * @return The open folder on the current screen, or null if there is none
592 */
593 Folder getOpenFolder() {
594 DragLayer dragLayer = mLauncher.getDragLayer();
595 int count = dragLayer.getChildCount();
596 for (int i = 0; i < count; i++) {
597 View child = dragLayer.getChildAt(i);
598 if (child instanceof Folder) {
599 Folder folder = (Folder) child;
600 if (folder.getInfo().opened)
601 return folder;
602 }
603 }
604 return null;
605 }
606
607 boolean isTouchActive() {
608 return mTouchState != TOUCH_STATE_REST;
609 }
610
611 public void removeAllWorkspaceScreens() {
612 // Disable all layout transitions before removing all pages to ensure that we don't get the
613 // transition animations competing with us changing the scroll when we add pages or the
614 // custom content screen
615 disableLayoutTransitions();
616
617 // Since we increment the current page when we call addCustomContentPage via bindScreens
618 // (and other places), we need to adjust the current page back when we clear the pages
619 if (hasCustomContent()) {
620 removeCustomContentPage();
621 }
622
623 // Remove the pages and clear the screen models
624 removeAllViews();
625 mScreenOrder.clear();
626 mWorkspaceScreens.clear();
627
628 // Re-enable the layout transitions
629 enableLayoutTransitions();
630 }
631
632 public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
633 // Find the index to insert this view into. If the empty screen exists, then
634 // insert it before that.
635 int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
636 if (insertIndex < 0) {
637 insertIndex = mScreenOrder.size();
638 }
639 return insertNewWorkspaceScreen(screenId, insertIndex);
640 }
641
642 public long insertNewWorkspaceScreen(long screenId) {
643 return insertNewWorkspaceScreen(screenId, getChildCount());
644 }
645
646 public long insertNewWorkspaceScreen(long screenId, int insertIndex) {
647 // Log to disk
648 Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId +
649 " at index: " + insertIndex, true);
650
651 if (mWorkspaceScreens.containsKey(screenId)) {
652 throw new RuntimeException("Screen id " + screenId + " already exists!");
653 }
654
655 CellLayout newScreen = (CellLayout)
656 mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null);
657
658 newScreen.setOnLongClickListener(mLongClickListener);
659 newScreen.setOnClickListener(mLauncher);
660 newScreen.setSoundEffectsEnabled(false);
661 mWorkspaceScreens.put(screenId, newScreen);
662 mScreenOrder.add(insertIndex, screenId);
663 addView(newScreen, insertIndex);
664 return screenId;
665 }
666
667 public void createCustomContentContainer() {
668 CellLayout customScreen = ((CellLayout) (mLauncher.getLayoutInflater().inflate(R.layout.workspace🔵
669 customScreen.disableBackground();
670 customScreen.disableDragTarget();
671 mWorkspaceScreens.put(CUSTOM_CONTENT_SCREEN_ID, customScreen);
672 mScreenOrder.add(0, CUSTOM_CONTENT_SCREEN_ID);
673 // We want no padding on the custom content
674 customScreen.setPadding(0, 0, 0, 0);
675 addFullScreenPage(customScreen);
676 // Ensure that the current page and default page are maintained.
677 mDefaultPage = mOriginalDefaultPage + 1;
678 // Update the custom content hint
679 if (mRestorePage != INVALID_RESTORE_PAGE) {
680 mRestorePage = mRestorePage + 1;
681 } else {
682 setCurrentPage(getCurrentPage() + 1);
683 }
684 }
685
686 public void removeCustomContentPage() {
687 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
688 if (customScreen == null) {
689 throw new RuntimeException("Expected custom content screen to exist");
690 }
691 mWorkspaceScreens.remove(CUSTOM_CONTENT_SCREEN_ID);
692 mScreenOrder.remove(CUSTOM_CONTENT_SCREEN_ID);
693 removeView(customScreen);
694 if (mCustomContentCallbacks != null) {
695 mCustomContentCallbacks.onScrollProgressChanged(0);
696 mCustomContentCallbacks.onHide();
697 }
698 mCustomContentCallbacks = null;
699 // Ensure that the current page and default page are maintained.
700 mDefaultPage = mOriginalDefaultPage - 1;
701 // Update the custom content hint
702 if (mRestorePage != INVALID_RESTORE_PAGE) {
703 mRestorePage = mRestorePage - 1;
704 } else {
705 setCurrentPage(getCurrentPage() - 1);
706 }
707 }
708
709 public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
710 String description) {
711 if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
712 throw new RuntimeException("Expected custom content screen to exist");
713 }
714
715 // Add the custom content to the full screen custom page
716 CellLayout customScreen = getScreenWithId(CUSTOM_CONTENT_SCREEN_ID);
717 int spanX = customScreen.getCountX();
718 int spanY = customScreen.getCountY();
719 CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, spanX, spanY);
720 lp.canReorder = false;
721 lp.isFullscreen = true;
722 if (customContent instanceof Insettable) {
723 ((Insettable)customContent).setInsets(mInsets);
724 }
725
726 // Verify that the child is removed from any existing parent.
727 if (customContent.getParent() instanceof ViewGroup) {
728 ViewGroup parent = (ViewGroup) customContent.getParent();
729 parent.removeView(customContent);
730 }
731 customScreen.removeAllViews();
732 customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
733 mCustomContentDescription = description;
734
735 mCustomContentCallbacks = callbacks;
736 }
737
738 public void addExtraEmptyScreenOnDrag() {
739 // Log to disk
740 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreenOnDrag()", true);
741
742 boolean lastChildOnScreen = false;
743 boolean childOnFinalScreen = false;
744
745 // Cancel any pending removal of empty screen
746 mRemoveEmptyScreenRunnable = null;
747
748 if (mDragSourceInternal != null) {
749 if (mDragSourceInternal.getChildCount() == 1) {
750 lastChildOnScreen = true;
751 }
752 CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
753 if (indexOfChild(cl) == getChildCount() - 1) {
754 childOnFinalScreen = true;
755 }
756 }
757
758 // If this is the last item on the final screen
759 if (lastChildOnScreen && childOnFinalScreen) {
760 return;
761 }
762 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
763 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
764 }
765 }
766
767 public boolean addExtraEmptyScreen() {
768 // Log to disk
769 Launcher.addDumpLog(TAG, "11683562 - addExtraEmptyScreen()", true);
770
771 if (!mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID)) {
772 insertNewWorkspaceScreen(EXTRA_EMPTY_SCREEN_ID);
773 return true;
774 }
775 return false;
776 }
777
778 private void convertFinalScreenToEmptyScreenIfNecessary() {
779 // Log to disk
780 Launcher.addDumpLog(TAG, "11683562 - convertFinalScreenToEmptyScreenIfNecessary()", true);
781 if (mLauncher.isWorkspaceLoading()) {
782 // Invalid and dangerous operation if workspace is loading
783 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
784 return;
785 }
786 if (hasExtraEmptyScreen() || (mScreenOrder.size() == 0)) {
787 return;
788 }
789 long finalScreenId = mScreenOrder.get(mScreenOrder.size() - 1);
790 if (finalScreenId == CUSTOM_CONTENT_SCREEN_ID) {
791 return;
792 }
793 CellLayout finalScreen = mWorkspaceScreens.get(finalScreenId);
794 // If the final screen is empty, convert it to the extra empty screen
795 if ((finalScreen.getShortcutsAndWidgets().getChildCount() == 0) && (!finalScreen.isDropPending())🔵
796 mWorkspaceScreens.remove(finalScreenId);
797 mScreenOrder.remove(finalScreenId);
798 // if this is the last non-custom content screen, convert it to the empty screen
799 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
800 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
801 // Update the model if we have changed any screens
802 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
803 Launcher.addDumpLog(TAG, "11683562 - extra empty screen: " + finalScreenId, true);
804 }
805 }
806
807 public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
808 removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
809 }
810
811 public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete, final int🔵
812 // Log to disk
813 Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
814 if (mLauncher.isWorkspaceLoading()) {
815 // Don't strip empty screens if the workspace is still loading
816 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
817 return;
818 }
819 if (delay > 0) {
820 postDelayed(new Runnable() {
821 @Override
822 public void run() {
823 removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
824 }
825 }, delay);
826 return;
827 }
828 convertFinalScreenToEmptyScreenIfNecessary();
829 if (hasExtraEmptyScreen()) {
830 int emptyIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
831 if (getNextPage() == emptyIndex) {
832 snapToPage(getNextPage() - 1, SNAP_OFF_EMPTY_SCREEN_DURATION);
833 fadeAndRemoveEmptyScreen(SNAP_OFF_EMPTY_SCREEN_DURATION, FADE_EMPTY_SCREEN_DURATION, onCo🔵
834 } else {
835 fadeAndRemoveEmptyScreen(0, FADE_EMPTY_SCREEN_DURATION, onComplete, stripEmptyScreens);
836 }
837 return;
838 } else if (stripEmptyScreens) {
839 // If we're not going to strip the empty screens after removing
840 // the extra empty screen, do it right away.
841 stripEmptyScreens();
842 }
843 if (onComplete != null) {
844 onComplete.run();
845 }
846 }
847
848 private void fadeAndRemoveEmptyScreen(int delay, int duration, final Runnable onComplete,
849 final boolean stripEmptyScreens) {
850 // Log to disk
851 // XXX: Do we need to update LM workspace screens below?
852 Launcher.addDumpLog(TAG, "11683562 - fadeAndRemoveEmptyScreen()", true);
853 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f);
854 PropertyValuesHolder bgAlpha = PropertyValuesHolder.ofFloat("backgroundAlpha", 0f);
855
856 final CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
857
858 mRemoveEmptyScreenRunnable = new Runnable() {
859 @Override
860 public void run() {
861 if (hasExtraEmptyScreen()) {
862 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
863 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
864 removeView(cl);
865 if (stripEmptyScreens) {
866 stripEmptyScreens();
867 }
868 }
869 }
870 };
871
872 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(cl, alpha, bgAlpha);
873 oa.setDuration(duration);
874 oa.setStartDelay(delay);
875 oa.addListener(new AnimatorListenerAdapter() {
876 @Override
877 public void onAnimationEnd(Animator animation) {
878 if (mRemoveEmptyScreenRunnable != null) {
879 mRemoveEmptyScreenRunnable.run();
880 }
881 if (onComplete != null) {
882 onComplete.run();
883 }
884 }
885 });
886 oa.start();
887 }
888
889 public boolean hasExtraEmptyScreen() {
890 int nScreens = getChildCount();
891 nScreens = nScreens - numCustomPages();
892 return mWorkspaceScreens.containsKey(EXTRA_EMPTY_SCREEN_ID) && nScreens > 1;
893 }
894
895 public long commitExtraEmptyScreen() {
896 // Log to disk
897 Launcher.addDumpLog(TAG, "11683562 - commitExtraEmptyScreen()", true);
898 if (mLauncher.isWorkspaceLoading()) {
899 // Invalid and dangerous operation if workspace is loading
900 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
901 return -1;
902 }
903 int index = getPageIndexForScreenId(EXTRA_EMPTY_SCREEN_ID);
904 CellLayout cl = mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID);
905 mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
906 mScreenOrder.remove(EXTRA_EMPTY_SCREEN_ID);
907 long newId = LauncherAppState.getLauncherProvider().generateNewScreenId();
908 mWorkspaceScreens.put(newId, cl);
909 mScreenOrder.add(newId);
910 // Update the page indicator marker
911 if (getPageIndicator() != null) {
912 getPageIndicator().updateMarker(index, getPageIndicatorMarker(index));
913 }
914 // Update the model for the new screen
915 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
916 return newId;
917 }
918
919 public CellLayout getScreenWithId(long screenId) {
920 CellLayout layout = mWorkspaceScreens.get(screenId);
921 return layout;
922 }
923
924 public long getIdForScreen(CellLayout layout) {
925 Iterator<Long> iter = mWorkspaceScreens.keySet().iterator();
926 while (iter.hasNext()) {
927 long id = iter.next();
928 if (mWorkspaceScreens.get(id) == layout) {
929 return id;
930 }
931 }
932 return -1;
933 }
934
935 public int getPageIndexForScreenId(long screenId) {
936 return indexOfChild(mWorkspaceScreens.get(screenId));
937 }
938
939 public long getScreenIdForPageIndex(int index) {
940 if (0 <= index && index < mScreenOrder.size()) {
941 return mScreenOrder.get(index);
942 }
943 return -1;
944 }
945
946 ArrayList<Long> getScreenOrder() {
947 return mScreenOrder;
948 }
949
950 public void stripEmptyScreens() {
951 // Log to disk
952 Launcher.addDumpLog(TAG, "11683562 - stripEmptyScreens()", true);
953 if (mLauncher.isWorkspaceLoading()) {
954 // Don't strip empty screens if the workspace is still loading.
955 // This is dangerous and can result in data loss.
956 Launcher.addDumpLog(TAG, " - workspace loading, skip", true);
957 return;
958 }
959 if (isPageMoving()) {
960 mStripScreensOnPageStopMoving = true;
961 return;
962 }
963 int currentPage = getNextPage();
964 ArrayList<Long> removeScreens = new ArrayList<Long>();
965 for (Long id : mWorkspaceScreens.keySet()) {
966 CellLayout cl = mWorkspaceScreens.get(id);
967 if ((id >= 0) && (cl.getShortcutsAndWidgets().getChildCount() == 0)) {
968 removeScreens.add(id);
969 }
970 }
971 // We enforce at least one page to add new items to. In the case that we remove the last
972 // such screen, we convert the last screen to the empty screen
973 int minScreens = 1 + numCustomPages();
974 int pageShift = 0;
975 for (Long id : removeScreens) {
976 Launcher.addDumpLog(TAG, "11683562 - removing id: " + id, true);
977 CellLayout cl = mWorkspaceScreens.get(id);
978 mWorkspaceScreens.remove(id);
979 mScreenOrder.remove(id);
980 if (getChildCount() > minScreens) {
981 if (indexOfChild(cl) < currentPage) {
982 pageShift++;
983 }
984 removeView(cl);
985 } else {
986 // if this is the last non-custom content screen, convert it to the empty screen
987 mRemoveEmptyScreenRunnable = null;
988 mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, cl);
989 mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
990 }
991 }
992 if (!removeScreens.isEmpty()) {
993 // Update the model if we have changed any screens
994 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
995 }
996 if (pageShift >= 0) {
997 setCurrentPage(currentPage - pageShift);
998 }
999 }
1000
1001 // See implementation for parameter definition.
1002 void addInScreen(View child, long container, long screenId,
1003 int x, int y, int spanX, int spanY) {
1004 addInScreen(child, container, screenId, x, y, spanX, spanY, false, false);
1005 }
1006
1007 // At bind time, we use the rank (screenId) to compute x and y for hotseat items.
1008 // See implementation for parameter definition.
1009 void addInScreenFromBind(View child, long container, long screenId, int x, int y,
1010 int spanX, int spanY) {
1011 addInScreen(child, container, screenId, x, y, spanX, spanY, false, true);
1012 }
1013
1014 // See implementation for parameter definition.
1015 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY,
1016 boolean insert) {
1017 addInScreen(child, container, screenId, x, y, spanX, spanY, insert, false);
1018 }
1019
1020 /**
1021 * Adds the specified child in the specified screen. The position and dimension of
1022 * the child are defined by x, y, spanX and spanY.
1023 *
1024 * @param child The child to add in one of the workspace's screens.
1025 * @param screenId The screen in which to add the child.
1026 * @param x The X position of the child in the screen's grid.
1027 * @param y The Y position of the child in the screen's grid.
1028 * @param spanX The number of cells spanned horizontally by the child.
1029 * @param spanY The number of cells spanned vertically by the child.
1030 * @param insert When true, the child is inserted at the beginning of the children list.
1031 * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute
1032 * the x and y position in which to place hotseat items. Otherwise
1033 * we use the x and y position to compute the rank.
1034 */
1035 void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, boole🔵
1036 if (container == Favorites.CONTAINER_DESKTOP) {
1037 if (getScreenWithId(screenId) == null) {
1038 Log.e(TAG, ("Skipping child, screenId " + screenId) + " not found");
1039 // DEBUGGING - Print out the stack trace to see where we are adding from
1040 new Throwable().printStackTrace();
1041 return;
1042 }
1043 }
1044 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
1045 // This should never happen
1046 throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
1047 }
1048 final CellLayout layout;
1049 if (container == Favorites.CONTAINER_HOTSEAT) {
1050 layout = mLauncher.getHotseat().getLayout();
1051 child.setOnKeyListener(new HotseatIconKeyEventListener());
1052 // Hide folder title in the hotseat
1053 if (child instanceof FolderIcon) {
1054 ((FolderIcon) (child)).setTextVisible(false);
1055 }
1056 if (computeXYFromRank) {
1057 x = mLauncher.getHotseat().getCellXFromOrder(((int) (screenId)));
1058 y = mLauncher.getHotseat().getCellYFromOrder(((int) (screenId)));
1059 } else {
1060 screenId = mLauncher.getHotseat().getOrderInHotseat(x, y);
1061 }
1062 } else {
1063 // Show folder title if not in the hotseat
1064 if (child instanceof FolderIcon) {
1065 ((FolderIcon) (child)).setTextVisible(true);
1066 }
1067 layout = getScreenWithId(screenId);
1068 child.setOnKeyListener(new IconKeyEventListener());
1069 }
1070 ViewGroup.LayoutParams genericLp = child.getLayoutParams();
1071 CellLayout.LayoutParams lp;
1072 if ((genericLp == null) || (!(genericLp instanceof CellLayout.LayoutParams))) {
1073 lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
1074 } else {
1075 lp = ((CellLayout.LayoutParams) (genericLp));
1076 lp.cellX = x;
1077 lp.cellY = y;
1078 lp.cellHSpan = spanX;
1079 lp.cellVSpan = spanY;
1080 }
1081 if ((spanX < 0) && (spanY < 0)) {
1082 lp.isLockedToGrid = false;
1083 }
1084 // Get the canonical child id to uniquely represent this view in this screen
1085 ItemInfo info = ((ItemInfo) (child.getTag()));
1086 int childId = mLauncher.getViewIdForItem(info);
1087 boolean markCellsAsOccupied = !(child instanceof Folder);
1088 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
1089 // TODO: This branch occurs when the workspace is adding views
1090 // outside of the defined grid
1091 // maybe we should be deleting these items from the LauncherModel?
1092 Launcher.addDumpLog(TAG, ((("Failed to add to item at (" + lp.cellX) + ",") + lp.cellY) + ") 🔵
1093 }
1094 if (!(child instanceof Folder)) {
1095 child.setHapticFeedbackEnabled(false);
1096 child.setOnLongClickListener(mLongClickListener);
1097 }
1098 if (child instanceof DropTarget) {
1099 mDragController.addDropTarget(((DropTarget) (child)));
1100 }
1101 }
1102
1103 /**
1104 * Called directly from a CellLayout (not by the framework), after we've been added as a
1105 * listener via setOnInterceptTouchEventListener(). This allows us to tell the CellLayout
1106 * that it should intercept touch events, which is not something that is normally supported.
1107 */
1108 @Override
1109 public boolean onTouch(View v, MotionEvent event) {
1110 return (workspaceInModalState() || (!isFinishedSwitchingState())) || ((!workspaceInModalState()) 🔵
1111 }
1112
1113 public boolean isSwitchingState() {
1114 return mIsSwitchingState;
1115 }
1116
1117 /** This differs from isSwitchingState in that we take into account how far the transition
1118 * has completed. */
1119 public boolean isFinishedSwitchingState() {
1120 return !mIsSwitchingState || (mTransitionProgress > 0.5f);
1121 }
1122
1123 protected void onWindowVisibilityChanged (int visibility) {
1124 mLauncher.onWindowVisibilityChanged(visibility);
1125 }
1126
1127 @Override
1128 public boolean dispatchUnhandledMove(View focused, int direction) {
1129 if (workspaceInModalState() || (!isFinishedSwitchingState())) {
1130 // when the home screens are shrunken, shouldn't allow side-scrolling
1131 return false;
1132 }
1133 return super.dispatchUnhandledMove(focused, direction);
1134 }
1135
1136 @Override
1137 public boolean onInterceptTouchEvent(MotionEvent ev) {
1138 switch (ev.getAction() & MotionEvent.ACTION_MASK) {
1139 case MotionEvent.ACTION_DOWN :
1140 mXDown = ev.getX();
1141 mYDown = ev.getY();
1142 mTouchDownTime = System.currentTimeMillis();
1143 break;
1144 case MotionEvent.ACTION_POINTER_UP :
1145 case MotionEvent.ACTION_UP :
1146 if (mTouchState == TOUCH_STATE_REST) {
1147 final CellLayout currentPage = ((CellLayout) (getChildAt(mCurrentPage)));
1148 if ((currentPage != null) && (!currentPage.lastDownOnOccupiedCell())) {
1149 onWallpaperTap(ev);
1150 }
1151 }
1152 }
1153 return super.onInterceptTouchEvent(ev);
1154 }
1155
1156 @Override
1157 public boolean onGenericMotionEvent(MotionEvent event) {
1158 // Ignore pointer scroll events if the custom content doesn't allow scrolling.
1159 if (((getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREEN_ID) && (mCustomContentCa🔵
1160 return false;
1161 }
1162 return super.onGenericMotionEvent(event);
1163 }
1164
1165 protected void reinflateWidgetsIfNecessary() {
1166 final int clCount = getChildCount();
1167 for (int i = 0; i < clCount; i++) {
1168 CellLayout cl = ((CellLayout) (getChildAt(i)));
1169 ShortcutAndWidgetContainer swc = cl.getShortcutsAndWidgets();
1170 final int itemCount = swc.getChildCount();
1171 for (int j = 0; j < itemCount; j++) {
1172 View v = swc.getChildAt(j);
1173 if ((v != null) && (v.getTag() instanceof LauncherAppWidgetInfo)) {
1174 LauncherAppWidgetInfo info = ((LauncherAppWidgetInfo) (v.getTag()));
1175 LauncherAppWidgetHostView lahv = ((LauncherAppWidgetHostView) (info.hostView));
1176 if ((lahv != null) && lahv.isReinflateRequired()) {
1177 mLauncher.removeAppWidget(info);
1178 // Remove the current widget which is inflated with the wrong orientation
1179 cl.removeView(lahv);
1180 mLauncher.bindAppWidget(info);
1181 }
1182 }
1183 }
1184 }
1185 }
1186
1187 @Override
1188 protected void determineScrollingStart(MotionEvent ev) {
1189 if (!isFinishedSwitchingState()) {
1190 return;
1191 }
1192 float deltaX = ev.getX() - mXDown;
1193 float absDeltaX = Math.abs(deltaX);
1194 float absDeltaY = Math.abs(ev.getY() - mYDown);
1195 if (Float.compare(absDeltaX, 0.0F) == 0) {
1196 return;
1197 }
1198 float slope = absDeltaY / absDeltaX;
1199 float theta = ((float) (Math.atan(slope)));
1200 if ((absDeltaX > mTouchSlop) || (absDeltaY > mTouchSlop)) {
1201 cancelCurrentPageLongPress();
1202 }
1203 boolean passRightSwipesToCustomContent = (mTouchDownTime - mCustomContentShowTime) > CUSTOM_CONTE🔵
1204 boolean swipeInIgnoreDirection = (isLayoutRtl()) ? deltaX < 0 : deltaX > 0;
1205 boolean onCustomContentScreen = getScreenIdForPageIndex(getCurrentPage()) == CUSTOM_CONTENT_SCREE🔵
1206 if ((swipeInIgnoreDirection && onCustomContentScreen) && passRightSwipesToCustomContent) {
1207 // Pass swipes to the right to the custom content page.
1208 return;
1209 }
1210 if ((onCustomContentScreen && (mCustomContentCallbacks != null)) && (!mCustomContentCallbacks.isS🔵
1211 // Don't allow workspace scrolling if the current custom content screen doesn't allow
1212 // scrolling.
1213 return;
1214 }
1215 if (theta > MAX_SWIPE_ANGLE) {
1216 // Above MAX_SWIPE_ANGLE, we don't want to ever start scrolling the workspace
1217 return;
1218 } else if (theta > START_DAMPING_TOUCH_SLOP_ANGLE) {
1219 // Above START_DAMPING_TOUCH_SLOP_ANGLE and below MAX_SWIPE_ANGLE, we want to
1220 // increase the touch slop to make it harder to begin scrolling the workspace. This
1221 // results in vertically scrolling widgets to more easily. The higher the angle, the
1222 // more we increase touch slop.
1223 theta -= START_DAMPING_TOUCH_SLOP_ANGLE;
1224 float extraRatio = ((float) (Math.sqrt(theta / (MAX_SWIPE_ANGLE - START_DAMPING_TOUCH_SLOP_AN🔵
1225 super.determineScrollingStart(ev, 1 + (TOUCH_SLOP_DAMPING_FACTOR * extraRatio));
1226 } else {
1227 // Below START_DAMPING_TOUCH_SLOP_ANGLE, we don't do anything special
1228 super.determineScrollingStart(ev);
1229 }
1230 }
1231
1232 protected void onPageBeginMoving() {
1233 super.onPageBeginMoving();
1234 if (isHardwareAccelerated()) {
1235 updateChildrenLayersEnabled(false);
1236 } else if (mNextPage != INVALID_PAGE) {
1237 // we're snapping to a particular screen
1238 enableChildrenCache(mCurrentPage, mNextPage);
1239 } else {
1240 // this is when user is actively dragging a particular screen, they might
1241 // swipe it either left or right (but we won't advance by more than one screen)
1242 enableChildrenCache(mCurrentPage - 1, mCurrentPage + 1);
1243 }
1244 }
1245
1246 protected void onPageEndMoving() {
1247 super.onPageEndMoving();
1248 if (isHardwareAccelerated()) {
1249 updateChildrenLayersEnabled(false);
1250 } else {
1251 clearChildrenCache();
1252 }
1253 if (mDragController.isDragging()) {
1254 if (workspaceInModalState()) {
1255 // If we are in springloaded mode, then force an event to check if the current touch
1256 // is under a new page (to scroll to)
1257 mDragController.forceTouchMove();
1258 }
1259 }
1260 if (mDelayedResizeRunnable != null) {
1261 mDelayedResizeRunnable.run();
1262 mDelayedResizeRunnable = null;
1263 }
1264 if (mDelayedSnapToPageRunnable != null) {
1265 mDelayedSnapToPageRunnable.run();
1266 mDelayedSnapToPageRunnable = null;
1267 }
1268 if (mStripScreensOnPageStopMoving) {
1269 stripEmptyScreens();
1270 mStripScreensOnPageStopMoving = false;
1271 }
1272 }
1273
1274 @Override
1275 protected void notifyPageSwitchListener() {
1276 super.notifyPageSwitchListener();
1277 Launcher.setScreen(getNextPage());
1278 if ((hasCustomContent() && (getNextPage() == 0)) && (!mCustomContentShowing)) {
1279 mCustomContentShowing = true;
1280 if (mCustomContentCallbacks != null) {
1281 mCustomContentCallbacks.onShow(false);
1282 mCustomContentShowTime = System.currentTimeMillis();
1283 mLauncher.updateVoiceButtonProxyVisible(false);
1284 }
1285 } else if ((hasCustomContent() && (getNextPage() != 0)) && mCustomContentShowing) {
1286 mCustomContentShowing = false;
1287 if (mCustomContentCallbacks != null) {
1288 mCustomContentCallbacks.onHide();
1289 mLauncher.resetQSBScroll();
1290 mLauncher.updateVoiceButtonProxyVisible(false);
1291 }
1292 }
1293 }
1294
1295 protected CustomContentCallbacks getCustomContentCallbacks() {
1296 return mCustomContentCallbacks;
1297 }
1298
1299 protected void setWallpaperDimension() {
1300 new AsyncTask<Void, Void, Void>() {
1301 public Void doInBackground(Void... args) {
1302 String spKey = WallpaperCropActivity.getSharedPreferencesKey();
1303 SharedPreferences sp = mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
1304 LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(), sp, m🔵
1305 return null;
1306 }
1307 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ((Void) (null)));
1308 }
1309
1310 protected void snapToPage(int whichPage, Runnable r) {
1311 snapToPage(whichPage, SLOW_PAGE_SNAP_ANIMATION_DURATION, r);
1312 }
1313
1314 protected void snapToPage(int whichPage, int duration, Runnable r) {
1315 if (mDelayedSnapToPageRunnable != null) {
1316 mDelayedSnapToPageRunnable.run();
1317 }
1318 mDelayedSnapToPageRunnable = r;
1319 snapToPage(whichPage, duration);
1320 }
1321
1322 public void snapToScreenId(long screenId) {
1323 snapToScreenId(screenId, null);
1324 }
1325
1326 protected void snapToScreenId(long screenId, Runnable r) {
1327 snapToPage(getPageIndexForScreenId(screenId), r);
1328 }
1329
1330 class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
1331 float mFinalOffset = 0.0F;
1332
1333 float mCurrentOffset = 0.5F;// to force an initial update
1334
1335
1336 boolean mWaitingForUpdate;
1337
1338 Choreographer mChoreographer;
1339
1340 Interpolator mInterpolator;
1341
1342 boolean mAnimating;
1343
1344 long mAnimationStartTime;
1345
1346 float mAnimationStartOffset;
1347
1348 private final int ANIMATION_DURATION = 250;
1349
1350 // Don't use all the wallpaper for parallax until you have at least this many pages
1351 private final int MIN_PARALLAX_PAGE_SPAN = 3;
1352
1353 int mNumScreens;
1354
1355 public WallpaperOffsetInterpolator() {
1356 mChoreographer = Choreographer.getInstance();
1357 mInterpolator = new DecelerateInterpolator(1.5F);
1358 }
1359
1360 @Override
1361 public void doFrame(long frameTimeNanos) {
1362 updateOffset(false);
1363 }
1364
1365 private void updateOffset(boolean force) {
1366 if (mWaitingForUpdate || force) {
1367 mWaitingForUpdate = false;
1368 if (computeScrollOffset() && (mWindowToken != null)) {
1369 try {
1370 mWallpaperManager.setWallpaperOffsets(mWindowToken, mWallpaperOffset.getCurrX(), 🔵
1371 setWallpaperOffsetSteps();
1372 } catch (java.lang.IllegalArgumentException e) {
1373 Log.e(TAG, "Error updating wallpaper offset: " + e);
1374 }
1375 }
1376 }
1377 }
1378
1379 public boolean computeScrollOffset() {
1380 final float oldOffset = mCurrentOffset;
1381 if (mAnimating) {
1382 long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
1383 float t0 = durationSinceAnimation / ((float) (ANIMATION_DURATION));
1384 float t1 = mInterpolator.getInterpolation(t0);
1385 mCurrentOffset = mAnimationStartOffset + ((mFinalOffset - mAnimationStartOffset) * t1);
1386 mAnimating = durationSinceAnimation < ANIMATION_DURATION;
1387 } else {
1388 mCurrentOffset = mFinalOffset;
1389 }
1390 if (Math.abs(mCurrentOffset - mFinalOffset) > 1.0E-7F) {
1391 scheduleUpdate();
1392 }
1393 if (Math.abs(oldOffset - mCurrentOffset) > 1.0E-7F) {
1394 return true;
1395 }
1396 return false;
1397 }
1398
1399 private float wallpaperOffsetForCurrentScroll() {
1400 if (getChildCount() <= 1) {
1401 return 0;
1402 }
1403 // Exclude the leftmost page
1404 int emptyExtraPages = numEmptyScreensToIgnore();
1405 int firstIndex = numCustomPages();
1406 // Exclude the last extra empty screen (if we have > MIN_PARALLAX_PAGE_SPAN pages)
1407 int lastIndex = (getChildCount() - 1) - emptyExtraPages;
1408 if (isLayoutRtl()) {
1409 int temp = firstIndex;
1410 firstIndex = lastIndex;
1411 lastIndex = temp;
1412 }
1413 int firstPageScrollX = getScrollForPage(firstIndex);
1414 int scrollRange = getScrollForPage(lastIndex) - firstPageScrollX;
1415 if (scrollRange == 0) {
1416 return 0;
1417 } else {
1418 // TODO: do different behavior if it's a live wallpaper?
1419 // Sometimes the left parameter of the pages is animated during a layout transition;
1420 // this parameter offsets it to keep the wallpaper from animating as well
1421 int adjustedScroll = (getScrollX() - firstPageScrollX) - getLayoutTransitionOffsetForPage🔵
1422 float offset = Math.min(1, adjustedScroll / ((float) (scrollRange)));
1423 offset = Math.max(0, offset);
1424 // Don't use up all the wallpaper parallax until you have at least
1425 // MIN_PARALLAX_PAGE_SPAN pages
1426 int numScrollingPages = getNumScreensExcludingEmptyAndCustom();
1427 int parallaxPageSpan;
1428 if (mWallpaperIsLiveWallpaper) {
1429 parallaxPageSpan = numScrollingPages - 1;
1430 } else {
1431 parallaxPageSpan = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages - 1);
1432 }
1433 mNumPagesForWallpaperParallax = parallaxPageSpan;
1434 // On RTL devices, push the wallpaper offset to the right if we don't have enough
1435 // pages (ie if numScrollingPages < MIN_PARALLAX_PAGE_SPAN)
1436 int padding = (isLayoutRtl()) ? (parallaxPageSpan - numScrollingPages) + 1 : 0;
1437 return (offset * ((padding + numScrollingPages) - 1)) / parallaxPageSpan;
1438 }
1439 }
1440
1441 private int numEmptyScreensToIgnore() {
1442 int numScrollingPages = getChildCount() - numCustomPages();
1443 if ((numScrollingPages >= MIN_PARALLAX_PAGE_SPAN) && hasExtraEmptyScreen()) {
1444 return 1;
1445 } else {
1446 return 0;
1447 }
1448 }
1449
1450 private int getNumScreensExcludingEmptyAndCustom() {
1451 int numScrollingPages = (getChildCount() - numEmptyScreensToIgnore()) - numCustomPages();
1452 return numScrollingPages;
1453 }
1454
1455 public void syncWithScroll() {
1456 float offset = wallpaperOffsetForCurrentScroll();
1457 mWallpaperOffset.setFinalX(offset);
1458 updateOffset(true);
1459 }
1460
1461 public float getCurrX() {
1462 return mCurrentOffset;
1463 }
1464
1465 public float getFinalX() {
1466 return mFinalOffset;
1467 }
1468
1469 private void animateToFinal() {
1470 mAnimating = true;
1471 mAnimationStartOffset = mCurrentOffset;
1472 mAnimationStartTime = System.currentTimeMillis();
1473 }
1474
1475 private void setWallpaperOffsetSteps() {
1476 // Set wallpaper offset steps (1 / (number of screens - 1))
1477 float xOffset = 1.0F / mNumPagesForWallpaperParallax;
1478 if (xOffset != mLastSetWallpaperOffsetSteps) {
1479 mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0F);
1480 mLastSetWallpaperOffsetSteps = xOffset;
1481 }
1482 }
1483
1484 public void setFinalX(float x) {
1485 scheduleUpdate();
1486 mFinalOffset = Math.max(0.0F, Math.min(x, 1.0F));
1487 if (getNumScreensExcludingEmptyAndCustom() != mNumScreens) {
1488 if (mNumScreens > 0) {
1489 // Don't animate if we're going from 0 screens
1490 animateToFinal();
1491 }
1492 mNumScreens = getNumScreensExcludingEmptyAndCustom();
1493 }
1494 }
1495
1496 private void scheduleUpdate() {
1497 if (!mWaitingForUpdate) {
1498 mChoreographer.postFrameCallback(this);
1499 mWaitingForUpdate = true;
1500 }
1501 }
1502
1503 public void jumpToFinal() {
1504 mCurrentOffset = mFinalOffset;
1505 }
1506 }
1507
1508 @Override
1509 public void computeScroll() {
1510 super.computeScroll();
1511 mWallpaperOffset.syncWithScroll();
1512 }
1513
1514 @Override
1515 public void announceForAccessibility(CharSequence text) {
1516 // Don't announce if apps is on top of us.
1517 if (!mLauncher.isAllAppsVisible()) {
1518 super.announceForAccessibility(text);
1519 }
1520 }
1521
1522 void showOutlines() {
1523 if ((!workspaceInModalState()) && (!mIsSwitchingState)) {
1524 if (mChildrenOutlineFadeOutAnimation != null) {
1525 mChildrenOutlineFadeOutAnimation.cancel();
1526 }
1527 if (mChildrenOutlineFadeInAnimation != null) {
1528 mChildrenOutlineFadeInAnimation.cancel();
1529 }
1530 mChildrenOutlineFadeInAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 1.0🔵
1531 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION);
1532 mChildrenOutlineFadeInAnimation.start();
1533 }
1534 }
1535
1536 void hideOutlines() {
1537 if ((!workspaceInModalState()) && (!mIsSwitchingState)) {
1538 if (mChildrenOutlineFadeInAnimation != null) {
1539 mChildrenOutlineFadeInAnimation.cancel();
1540 }
1541 if (mChildrenOutlineFadeOutAnimation != null) {
1542 mChildrenOutlineFadeOutAnimation.cancel();
1543 }
1544 mChildrenOutlineFadeOutAnimation = LauncherAnimUtils.ofFloat(this, "childrenOutlineAlpha", 0.🔵
1545 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION);
1546 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY);
1547 mChildrenOutlineFadeOutAnimation.start();
1548 }
1549 }
1550
1551 public void showOutlinesTemporarily() {
1552 if (!mIsPageMoving && !isTouchActive()) {
1553 snapToPage(mCurrentPage);
1554 }
1555 }
1556
1557 public void setChildrenOutlineAlpha(float alpha) {
1558 mChildrenOutlineAlpha = alpha;
1559 for (int i = 0; i < getChildCount(); i++) {
1560 CellLayout cl = (CellLayout) getChildAt(i);
1561 cl.setBackgroundAlpha(alpha);
1562 }
1563 }
1564
1565 public float getChildrenOutlineAlpha() {
1566 return mChildrenOutlineAlpha;
1567 }
1568
1569 private void animateBackgroundGradient(float finalAlpha, boolean animated) {
1570 final DragLayer dragLayer = mLauncher.getDragLayer();
1571 if (mBackgroundFadeInAnimation != null) {
1572 mBackgroundFadeInAnimation.cancel();
1573 mBackgroundFadeInAnimation = null;
1574 }
1575 if (mBackgroundFadeOutAnimation != null) {
1576 mBackgroundFadeOutAnimation.cancel();
1577 mBackgroundFadeOutAnimation = null;
1578 }
1579 float startAlpha = dragLayer.getBackgroundAlpha();
1580 if (finalAlpha != startAlpha) {
1581 if (animated) {
1582 mBackgroundFadeOutAnimation = LauncherAnimUtils.ofFloat(this, startAlpha, finalAlpha);
1583 mBackgroundFadeOutAnimation.addUpdateListener(new AnimatorUpdateListener() {
1584 public void onAnimationUpdate(ValueAnimator animation) {
1585 dragLayer.setBackgroundAlpha(((Float) (animation.getAnimatedValue())).floatValue(🔵
1586 }
1587 });
1588 mBackgroundFadeOutAnimation.setInterpolator(new DecelerateInterpolator(1.5F));
1589 mBackgroundFadeOutAnimation.setDuration(BACKGROUND_FADE_OUT_DURATION);
1590 mBackgroundFadeOutAnimation.start();
1591 } else {
1592 dragLayer.setBackgroundAlpha(finalAlpha);
1593 }
1594 }
1595 }
1596
1597 float backgroundAlphaInterpolator(float r) {
1598 float pivotA = 0.1f;
1599 float pivotB = 0.4f;
1600 if (r < pivotA) {
1601 return 0;
1602 } else if (r > pivotB) {
1603 return 1.0f;
1604 } else {
1605 return (r - pivotA)/(pivotB - pivotA);
1606 }
1607 }
1608
1609 private void updatePageAlphaValues(int screenCenter) {
1610 boolean isInOverscroll = (mOverScrollX < 0) || (mOverScrollX > mMaxScrollX);
1611 if (((mWorkspaceFadeInAdjacentScreens && (!workspaceInModalState())) && (!mIsSwitchingState)) && 🔵
1612 for (int i = numCustomPages(); i < getChildCount(); i++) {
1613 CellLayout child = ((CellLayout) (getChildAt(i)));
1614 if (child != null) {
1615 float scrollProgress = getScrollProgress(screenCenter, child, i);
1616 float alpha = 1 - Math.abs(scrollProgress);
1617 child.getShortcutsAndWidgets().setAlpha(alpha);
1618 //child.setBackgroundAlphaMultiplier(1 - alpha);
1619 }
1620 }
1621 }
1622 }
1623
1624 private void setChildrenBackgroundAlphaMultipliers(float a) {
1625 for (int i = 0; i < getChildCount(); i++) {
1626 CellLayout child = (CellLayout) getChildAt(i);
1627 child.setBackgroundAlphaMultiplier(a);
1628 }
1629 }
1630
1631 public boolean hasCustomContent() {
1632 return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID);
1633 }
1634
1635 public int numCustomPages() {
1636 return hasCustomContent() ? 1 : 0;
1637 }
1638
1639 public boolean isOnOrMovingToCustomContent() {
1640 return hasCustomContent() && getNextPage() == 0;
1641 }
1642
1643 private void updateStateForCustomContent(int screenCenter) {
1644 float translationX = 0;
1645 float progress = 0;
1646 if (hasCustomContent()) {
1647 int index = mScreenOrder.indexOf(CUSTOM_CONTENT_SCREEN_ID);
1648 int scrollDelta = (getScrollX() - getScrollForPage(index)) - getLayoutTransitionOffsetForPage🔵
1649 float scrollRange = getScrollForPage(index + 1) - getScrollForPage(index);
1650 translationX = scrollRange - scrollDelta;
1651 progress = (scrollRange - scrollDelta) / scrollRange;
1652 if (isLayoutRtl()) {
1653 translationX = Math.min(0, translationX);
1654 } else {
1655 translationX = Math.max(0, translationX);
1656 }
1657 progress = Math.max(0, progress);
1658 }
1659 if (Float.compare(progress, mLastCustomContentScrollProgress) == 0) {
1660 return;
1661 }
1662 CellLayout cc = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1663 if (((progress > 0) && (cc.getVisibility() != VISIBLE)) && (!workspaceInModalState())) {
1664 cc.setVisibility(VISIBLE);
1665 }
1666 mLastCustomContentScrollProgress = progress;
1667 mLauncher.getDragLayer().setBackgroundAlpha(progress * 0.8F);
1668 if (mLauncher.getHotseat() != null) {
1669 mLauncher.getHotseat().setTranslationX(translationX);
1670 }
1671 if (getPageIndicator() != null) {
1672 getPageIndicator().setTranslationX(translationX);
1673 }
1674 if (mCustomContentCallbacks != null) {
1675 mCustomContentCallbacks.onScrollProgressChanged(progress);
1676 }
1677 }
1678
1679 @Override
1680 protected OnClickListener getPageIndicatorClickListener() {
1681 AccessibilityManager am = (AccessibilityManager)
1682 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1683 if (!am.isTouchExplorationEnabled()) {
1684 return null;
1685 }
1686 OnClickListener listener = new OnClickListener() {
1687 @Override
1688 public void onClick(View arg0) {
1689 enterOverviewMode();
1690 }
1691 };
1692 return listener;
1693 }
1694
1695 @Override
1696 protected void screenScrolled(int screenCenter) {
1697 final boolean isRtl = isLayoutRtl();
1698 super.screenScrolled(screenCenter);
1699 updatePageAlphaValues(screenCenter);
1700 updateStateForCustomContent(screenCenter);
1701 enableHwLayersOnVisiblePages();
1702 boolean shouldOverScroll = (mOverScrollX < 0) || (mOverScrollX > mMaxScrollX);
1703 if (shouldOverScroll) {
1704 int index = 0;
1705 final int lowerIndex = 0;
1706 final int upperIndex = getChildCount() - 1;
1707 final boolean isLeftPage = mOverScrollX < 0;
1708 index = (((!isRtl) && isLeftPage) || (isRtl && (!isLeftPage))) ? lowerIndex : upperIndex;
1709 CellLayout cl = ((CellLayout) (getChildAt(index)));
1710 float effect = Math.abs(mOverScrollEffect);
1711 cl.setOverScrollAmount(Math.abs(effect), isLeftPage);
1712 mOverscrollEffectSet = true;
1713 } else if (mOverscrollEffectSet && (getChildCount() > 0)) {
1714 mOverscrollEffectSet = false;
1715 ((CellLayout) (getChildAt(0))).setOverScrollAmount(0, false);
1716 ((CellLayout) (getChildAt(getChildCount() - 1))).setOverScrollAmount(0, false);
1717 }
1718 }
1719
1720 @Override
1721 protected void overScroll(float amount) {
1722 boolean shouldOverScroll = ((amount < 0) && ((!hasCustomContent()) || isLayoutRtl())) || ((amount🔵
1723 if (shouldOverScroll) {
1724 dampedOverScroll(amount);
1725 mOverScrollEffect = acceleratedOverFactor(amount);
1726 } else {
1727 mOverScrollEffect = 0;
1728 }
1729 }
1730
1731 protected void onAttachedToWindow() {
1732 super.onAttachedToWindow();
1733 mWindowToken = getWindowToken();
1734 computeScroll();
1735 mDragController.setWindowToken(mWindowToken);
1736 }
1737
1738 protected void onDetachedFromWindow() {
1739 super.onDetachedFromWindow();
1740 mWindowToken = null;
1741 }
1742
1743 protected void onResume() {
1744 if (getPageIndicator() != null) {
1745 // In case accessibility state has changed, we need to perform this on every
1746 // attach to window
1747 OnClickListener listener = getPageIndicatorClickListener();
1748 if (listener != null) {
1749 getPageIndicator().setOnClickListener(listener);
1750 }
1751 }
1752 AccessibilityManager am = (AccessibilityManager)
1753 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1754 sAccessibilityEnabled = am.isEnabled();
1755
1756 // Update wallpaper dimensions if they were changed since last onResume
1757 // (we also always set the wallpaper dimensions in the constructor)
1758 if (LauncherAppState.getInstance().hasWallpaperChangedSinceLastCheck()) {
1759 setWallpaperDimension();
1760 }
1761 mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
1762 // Force the wallpaper offset steps to be set again, because another app might have changed
1763 // them
1764 mLastSetWallpaperOffsetSteps = 0f;
1765 }
1766
1767 @Override
1768 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1769 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
1770 mWallpaperOffset.syncWithScroll();
1771 mWallpaperOffset.jumpToFinal();
1772 }
1773 super.onLayout(changed, left, top, right, bottom);
1774 }
1775
1776 @Override
1777 protected void onDraw(Canvas canvas) {
1778 super.onDraw(canvas);
1779 // Call back to LauncherModel to finish binding after the first draw
1780 post(mBindPages);
1781 }
1782
1783 @Override
1784 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1785 if (!mLauncher.isAllAppsVisible()) {
1786 final Folder openFolder = getOpenFolder();
1787 if (openFolder != null) {
1788 return openFolder.requestFocus(direction, previouslyFocusedRect);
1789 } else {
1790 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
1791 }
1792 }
1793 return false;
1794 }
1795
1796 @Override
1797 public int getDescendantFocusability() {
1798 if (workspaceInModalState()) {
1799 return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
1800 }
1801 return super.getDescendantFocusability();
1802 }
1803
1804 @Override
1805 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1806 if (!mLauncher.isAllAppsVisible()) {
1807 final Folder openFolder = getOpenFolder();
1808 if (openFolder != null) {
1809 openFolder.addFocusables(views, direction);
1810 } else {
1811 super.addFocusables(views, direction, focusableMode);
1812 }
1813 }
1814 }
1815
1816 public boolean workspaceInModalState() {
1817 return mState != State.NORMAL;
1818 }
1819
1820 void enableChildrenCache(int fromPage, int toPage) {
1821 if (fromPage > toPage) {
1822 final int temp = fromPage;
1823 fromPage = toPage;
1824 toPage = temp;
1825 }
1826
1827 final int screenCount = getChildCount();
1828
1829 fromPage = Math.max(fromPage, 0);
1830 toPage = Math.min(toPage, screenCount - 1);
1831
1832 for (int i = fromPage; i <= toPage; i++) {
1833 final CellLayout layout = (CellLayout) getChildAt(i);
1834 layout.setChildrenDrawnWithCacheEnabled(true);
1835 layout.setChildrenDrawingCacheEnabled(true);
1836 }
1837 }
1838
1839 void clearChildrenCache() {
1840 final int screenCount = getChildCount();
1841 for (int i = 0; i < screenCount; i++) {
1842 final CellLayout layout = (CellLayout) getChildAt(i);
1843 layout.setChildrenDrawnWithCacheEnabled(false);
1844 // In software mode, we don't want the items to continue to be drawn into bitmaps
1845 if (!isHardwareAccelerated()) {
1846 layout.setChildrenDrawingCacheEnabled(false);
1847 }
1848 }
1849 }
1850
1851 private void updateChildrenLayersEnabled(boolean force) {
1852 boolean small = (mState == State.OVERVIEW) || mIsSwitchingState;
1853 boolean enableChildrenLayers = ((force || small) || mAnimatingViewIntoPlace) || isPageMoving();
1854 if (enableChildrenLayers != mChildrenLayersEnabled) {
1855 mChildrenLayersEnabled = enableChildrenLayers;
1856 if (mChildrenLayersEnabled) {
1857 enableHwLayersOnVisiblePages();
1858 } else {
1859 for (int i = 0; i < getPageCount(); i++) {
1860 final CellLayout cl = ((CellLayout) (getChildAt(i)));
1861 cl.enableHardwareLayer(false);
1862 }
1863 }
1864 }
1865 }
1866
1867 private void enableHwLayersOnVisiblePages() {
1868 if (mChildrenLayersEnabled) {
1869 final int screenCount = getChildCount();
1870 getVisiblePages(mTempVisiblePagesRange);
1871 int leftScreen = mTempVisiblePagesRange[0];
1872 int rightScreen = mTempVisiblePagesRange[1];
1873 if (leftScreen == rightScreen) {
1874 // make sure we're caching at least two pages always
1875 if (rightScreen < screenCount - 1) {
1876 rightScreen++;
1877 } else if (leftScreen > 0) {
1878 leftScreen--;
1879 }
1880 }
1881
1882 final CellLayout customScreen = mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID);
1883 for (int i = 0; i < screenCount; i++) {
1884 final CellLayout layout = (CellLayout) getPageAt(i);
1885
1886 // enable layers between left and right screen inclusive, except for the
1887 // customScreen, which may animate its content during transitions.
1888 boolean enableLayer = layout != customScreen &&
1889 leftScreen <= i && i <= rightScreen && shouldDrawChild(layout);
1890 layout.enableHardwareLayer(enableLayer);
1891 }
1892 }
1893 }
1894
1895 public void buildPageHardwareLayers() {
1896 // force layers to be enabled just for the call to buildLayer
1897 updateChildrenLayersEnabled(true);
1898 if (getWindowToken() != null) {
1899 final int childCount = getChildCount();
1900 for (int i = 0; i < childCount; i++) {
1901 CellLayout cl = (CellLayout) getChildAt(i);
1902 cl.buildHardwareLayer();
1903 }
1904 }
1905 updateChildrenLayersEnabled(false);
1906 }
1907
1908 protected void onWallpaperTap(MotionEvent ev) {
1909 final int[] position = mTempCell;
1910 getLocationOnScreen(position);
1911
1912 int pointerIndex = ev.getActionIndex();
1913 position[0] += (int) ev.getX(pointerIndex);
1914 position[1] += (int) ev.getY(pointerIndex);
1915
1916 mWallpaperManager.sendWallpaperCommand(getWindowToken(),
1917 ev.getAction() == MotionEvent.ACTION_UP
1918 ? WallpaperManager.COMMAND_TAP : WallpaperManager.COMMAND_SECONDARY_TAP,
1919 position[0], position[1], 0, null);
1920 }
1921
1922 /* This interpolator emulates the rate at which the perceived scale of an object changes
1923 as its distance from a camera increases. When this interpolator is applied to a scale
1924 animation on a view, it evokes the sense that the object is shrinking due to moving away
1925 from the camera.
1926 */
1927 static class ZInterpolator implements TimeInterpolator {
1928 private float focalLength;
1929
1930 public ZInterpolator(float foc) {
1931 focalLength = foc;
1932 }
1933
1934 public float getInterpolation(float input) {
1935 return (1.0F - (focalLength / (focalLength + input))) / (1.0F - (focalLength / (focalLength +🔵
1936 }
1937 }
1938
1939 /* The exact reverse of ZInterpolator. */
1940 static class InverseZInterpolator implements TimeInterpolator {
1941 private ZInterpolator zInterpolator;
1942
1943 public InverseZInterpolator(float foc) {
1944 zInterpolator = new ZInterpolator(foc);
1945 }
1946
1947 public float getInterpolation(float input) {
1948 return 1 - zInterpolator.getInterpolation(1 - input);
1949 }
1950 }
1951
1952 /* ZInterpolator compounded with an ease-out. */
1953 static class ZoomOutInterpolator implements TimeInterpolator {
1954 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(0.75F);
1955
1956 private final ZInterpolator zInterpolator = new ZInterpolator(0.13F);
1957
1958 public float getInterpolation(float input) {
1959 return decelerate.getInterpolation(zInterpolator.getInterpolation(input));
1960 }
1961 }
1962
1963 /* InvereZInterpolator compounded with an ease-out. */
1964 static class ZoomInInterpolator implements TimeInterpolator {
1965 private final InverseZInterpolator inverseZInterpolator = new InverseZInterpolator(0.35F);
1966
1967 private final DecelerateInterpolator decelerate = new DecelerateInterpolator(3.0F);
1968
1969 public float getInterpolation(float input) {
1970 return decelerate.getInterpolation(inverseZInterpolator.getInterpolation(input));
1971 }
1972 }
1973
1974 private final ZoomInInterpolator mZoomInInterpolator = new ZoomInInterpolator();
1975
1976 /*
1977 *
1978 * We call these methods (onDragStartedWithItemSpans/onDragStartedWithSize) whenever we
1979 * start a drag in Launcher, regardless of whether the drag has ever entered the Workspace
1980 *
1981 * These methods mark the appropriate pages as accepting drops (which alters their visual
1982 * appearance).
1983 *
1984 */
1985 private static Rect getDrawableBounds(Drawable d) {
1986 Rect bounds = new Rect();
1987 d.copyBounds(bounds);
1988 if ((bounds.width() == 0) || (bounds.height() == 0)) {
1989 bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
1990 } else {
1991 bounds.offsetTo(0, 0);
1992 }
1993 if (d instanceof PreloadIconDrawable) {
1994 int inset = -((PreloadIconDrawable) (d)).getOutset();
1995 bounds.inset(inset, inset);
1996 }
1997 return bounds;
1998 }
1999
2000 public void onExternalDragStartedWithItem(View v) {
2001 // Compose a drag bitmap with the view scaled to the icon size
2002 LauncherAppState app = LauncherAppState.getInstance();
2003 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2004 int iconSize = grid.iconSizePx;
2005 int bmpWidth = v.getMeasuredWidth();
2006 int bmpHeight = v.getMeasuredHeight();
2007 // If this is a text view, use its drawable instead
2008 if (v instanceof TextView) {
2009 TextView tv = ((TextView) (v));
2010 Drawable d = tv.getCompoundDrawables()[1];
2011 Rect bounds = getDrawableBounds(d);
2012 bmpWidth = bounds.width();
2013 bmpHeight = bounds.height();
2014 }
2015 // Compose the bitmap to create the icon from
2016 Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
2017 mCanvas.setBitmap(b);
2018 drawDragView(v, mCanvas, 0);
2019 mCanvas.setBitmap(null);
2020 // The outline is used to visualize where the item will land if dropped
2021 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
2022 }
2023
2024 public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
2025 int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
2026 // The outline is used to visualize where the item will land if dropped
2027 mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
2028 }
2029
2030 public void exitWidgetResizeMode() {
2031 DragLayer dragLayer = mLauncher.getDragLayer();
2032 dragLayer.clearAllResizeFrames();
2033 }
2034
2035 private void initAnimationArrays() {
2036 final int childCount = getChildCount();
2037 if (mLastChildCount == childCount) return;
2038
2039 mOldBackgroundAlphas = new float[childCount];
2040 mOldAlphas = new float[childCount];
2041 mNewBackgroundAlphas = new float[childCount];
2042 mNewAlphas = new float[childCount];
2043 }
2044
2045 Animator getChangeStateAnimation(final State state, boolean animated, ArrayList<View> layerViews) {
2046 return getChangeStateAnimation(state, animated, 0, -1, layerViews);
2047 }
2048
2049 @Override
2050 protected void getFreeScrollPageRange(int[] range) {
2051 getOverviewModePages(range);
2052 }
2053
2054 private void getOverviewModePages(int[] range) {
2055 int start = numCustomPages();
2056 int end = getChildCount() - 1;
2057 range[0] = Math.max(0, Math.min(start, getChildCount() - 1));
2058 range[1] = Math.max(0, end);
2059 }
2060
2061 protected void onStartReordering() {
2062 super.onStartReordering();
2063 showOutlines();
2064 // Reordering handles its own animations, disable the automatic ones.
2065 disableLayoutTransitions();
2066 }
2067
2068 protected void onEndReordering() {
2069 super.onEndReordering();
2070 if (mLauncher.isWorkspaceLoading()) {
2071 // Invalid and dangerous operation if workspace is loading
2072 return;
2073 }
2074 hideOutlines();
2075 mScreenOrder.clear();
2076 int count = getChildCount();
2077 for (int i = 0; i < count; i++) {
2078 CellLayout cl = ((CellLayout) (getChildAt(i)));
2079 mScreenOrder.add(getIdForScreen(cl));
2080 }
2081 mLauncher.getModel().updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
2082 // Re-enable auto layout transitions for page deletion.
2083 enableLayoutTransitions();
2084 }
2085
2086 public boolean isInOverviewMode() {
2087 return mState == State.OVERVIEW;
2088 }
2089
2090 public boolean enterOverviewMode() {
2091 if (mTouchState != TOUCH_STATE_REST) {
2092 return false;
2093 }
2094 enableOverviewMode(true, -1, true);
2095 return true;
2096 }
2097
2098 public void exitOverviewMode(boolean animated) {
2099 exitOverviewMode(-1, animated);
2100 }
2101
2102 public void exitOverviewMode(int snapPage, boolean animated) {
2103 enableOverviewMode(false, snapPage, animated);
2104 }
2105
2106 private void enableOverviewMode(boolean enable, int snapPage, boolean animated) {
2107 State finalState = Workspace.State.OVERVIEW;
2108 if (!enable) {
2109 finalState = Workspace.State.NORMAL;
2110 }
2111
2112 Animator workspaceAnim = getChangeStateAnimation(finalState, animated, 0, snapPage);
2113 if (workspaceAnim != null) {
2114 onTransitionPrepare();
2115 workspaceAnim.addListener(new AnimatorListenerAdapter() {
2116 @Override
2117 public void onAnimationEnd(Animator arg0) {
2118 onTransitionEnd();
2119 }
2120 });
2121 workspaceAnim.start();
2122 }
2123 }
2124
2125 int getOverviewModeTranslationY() {
2126 LauncherAppState app = LauncherAppState.getInstance();
2127 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2128 Rect overviewBar = grid.getOverviewModeButtonBarRect();
2129
2130 int availableHeight = getViewportHeight();
2131 int scaledHeight = (int) (mOverviewModeShrinkFactor * getNormalChildHeight());
2132 int offsetFromTopEdge = (availableHeight - scaledHeight) / 2;
2133 int offsetToCenterInOverview = (availableHeight - mInsets.top - overviewBar.height()
2134 - scaledHeight) / 2;
2135
2136 return -offsetFromTopEdge + mInsets.top + offsetToCenterInOverview;
2137 }
2138
2139 boolean shouldVoiceButtonProxyBeVisible() {
2140 if (isOnOrMovingToCustomContent()) {
2141 return false;
2142 }
2143 if (mState != State.NORMAL) {
2144 return false;
2145 }
2146 return true;
2147 }
2148
2149 public void updateInteractionForState() {
2150 if (mState != State.NORMAL) {
2151 mLauncher.onInteractionBegin();
2152 } else {
2153 mLauncher.onInteractionEnd();
2154 }
2155 }
2156
2157 private void setState(State state) {
2158 mState = state;
2159 updateInteractionForState();
2160 updateAccessibilityFlags();
2161 }
2162
2163 State getState() {
2164 return mState;
2165 }
2166
2167 private void updateAccessibilityFlags() {
2168 int accessible = mState == State.NORMAL ?
2169 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
2170 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
2171 setImportantForAccessibility(accessible);
2172 }
2173
2174 private static final int HIDE_WORKSPACE_DURATION = 100;
2175
2176 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
2177 return getChangeStateAnimation(state, animated, delay, snapPage, null);
2178 }
2179
2180 Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage, ArrayL🔵
2181 if (mState == state) {
2182 return null;
2183 }
2184 // Initialize animation arrays for the first time if necessary
2185 initAnimationArrays();
2186 AnimatorSet anim = (animated) ? LauncherAnimUtils.createAnimatorSet() : null;
2187 final State oldState = mState;
2188 final boolean oldStateIsNormal = oldState == State.NORMAL;
2189 final boolean oldStateIsSpringLoaded = oldState == State.SPRING_LOADED;
2190 final boolean oldStateIsNormalHidden = oldState == State.NORMAL_HIDDEN;
2191 final boolean oldStateIsOverviewHidden = oldState == State.OVERVIEW_HIDDEN;
2192 final boolean oldStateIsOverview = oldState == State.OVERVIEW;
2193 setState(state);
2194 final boolean stateIsNormal = state == State.NORMAL;
2195 final boolean stateIsSpringLoaded = state == State.SPRING_LOADED;
2196 final boolean stateIsNormalHidden = state == State.NORMAL_HIDDEN;
2197 final boolean stateIsOverviewHidden = state == State.OVERVIEW_HIDDEN;
2198 final boolean stateIsOverview = state == State.OVERVIEW;
2199 float finalBackgroundAlpha = (stateIsSpringLoaded || stateIsOverview) ? 1.0F : 0.0F;
2200 float finalHotseatAndPageIndicatorAlpha = (stateIsNormal || stateIsSpringLoaded) ? 1.0F : 0.0F;
2201 float finalOverviewPanelAlpha = (stateIsOverview) ? 1.0F : 0.0F;
2202 float finalSearchBarAlpha = (!stateIsNormal) ? 0.0F : 1.0F;
2203 float finalWorkspaceTranslationY = (stateIsOverview || stateIsOverviewHidden) ? getOverviewModeTr🔵
2204 boolean workspaceToAllApps = oldStateIsNormal && stateIsNormalHidden;
2205 boolean overviewToAllApps = oldStateIsOverview && stateIsOverviewHidden;
2206 boolean allAppsToWorkspace = stateIsNormalHidden && stateIsNormal;
2207 boolean workspaceToOverview = oldStateIsNormal && stateIsOverview;
2208 boolean overviewToWorkspace = oldStateIsOverview && stateIsNormal;
2209 mNewScale = 1.0F;
2210 if (oldStateIsOverview) {
2211 disableFreeScroll();
2212 } else if (stateIsOverview) {
2213 enableFreeScroll();
2214 }
2215 if (state != State.NORMAL) {
2216 if (stateIsSpringLoaded) {
2217 mNewScale = mSpringLoadedShrinkFactor;
2218 } else if (stateIsOverview || stateIsOverviewHidden) {
2219 mNewScale = mOverviewModeShrinkFactor;
2220 }
2221 }
2222 final int duration;
2223 if (workspaceToAllApps || overviewToAllApps) {
2224 //getResources().getInteger(R.integer.config_workspaceUnshrinkTime);
2225 duration = HIDE_WORKSPACE_DURATION;
2226 } else if (workspaceToOverview || overviewToWorkspace) {
2227 duration = getResources().getInteger(R.integer.config_overviewTransitionTime);
2228 } else {
2229 duration = getResources().getInteger(R.integer.config_appsCustomizeWorkspaceShrinkTime);
2230 }
2231 if (snapPage == (-1)) {
2232 snapPage = getPageNearestToCenterOfScreen();
2233 }
2234 snapToPage(snapPage, duration, mZoomInInterpolator);
2235 for (int i = 0; i < getChildCount(); i++) {
2236 final CellLayout cl = ((CellLayout) (getChildAt(i)));
2237 boolean isCurrentPage = i == snapPage;
2238 float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
2239 float finalAlpha;
2240 if (stateIsNormalHidden || stateIsOverviewHidden) {
2241 finalAlpha = 0.0F;
2242 } else if (stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
2243 finalAlpha = ((i == snapPage) || (i < numCustomPages())) ? 1.0F : 0.0F;
2244 } else {
2245 finalAlpha = 1.0F;
2246 }
2247 // If we are animating to/from the small state, then hide the side pages and fade the
2248 // current page in
2249 if (!mIsSwitchingState) {
2250 if (workspaceToAllApps || allAppsToWorkspace) {
2251 if (allAppsToWorkspace && isCurrentPage) {
2252 initialAlpha = 0.0F;
2253 } else if (!isCurrentPage) {
2254 initialAlpha = finalAlpha = 0.0F;
2255 }
2256 cl.setShortcutAndWidgetAlpha(initialAlpha);
2257 }
2258 }
2259 mOldAlphas[i] = initialAlpha;
2260 mNewAlphas[i] = finalAlpha;
2261 if (animated) {
2262 mOldBackgroundAlphas[i] = cl.getBackgroundAlpha();
2263 mNewBackgroundAlphas[i] = finalBackgroundAlpha;
2264 } else {
2265 cl.setBackgroundAlpha(finalBackgroundAlpha);
2266 cl.setShortcutAndWidgetAlpha(finalAlpha);
2267 }
2268 }
2269 final View searchBar = mLauncher.getQsbBar();
2270 final View overviewPanel = mLauncher.getOverviewPanel();
2271 final View hotseat = mLauncher.getHotseat();
2272 final View pageIndicator = getPageIndicator();
2273 if (animated) {
2274 LauncherViewPropertyAnimator scale = new LauncherViewPropertyAnimator(this);
2275 scale.scaleX(mNewScale).scaleY(mNewScale).translationY(finalWorkspaceTranslationY).setDuratio🔵
2276 anim.play(scale);
2277 for (int index = 0; index < getChildCount(); index++) {
2278 final int i = index;
2279 final CellLayout cl = ((CellLayout) (getChildAt(i)));
2280 float currentAlpha = cl.getShortcutsAndWidgets().getAlpha();
2281 if ((mOldAlphas[i] == 0) && (mNewAlphas[i] == 0)) {
2282 cl.setBackgroundAlpha(mNewBackgroundAlphas[i]);
2283 cl.setShortcutAndWidgetAlpha(mNewAlphas[i]);
2284 } else {
2285 if (layerViews != null) {
2286 layerViews.add(cl);
2287 }
2288 if ((mOldAlphas[i] != mNewAlphas[i]) || (currentAlpha != mNewAlphas[i])) {
2289 LauncherViewPropertyAnimator alphaAnim = new LauncherViewPropertyAnimator(cl.getS🔵
2290 alphaAnim.alpha(mNewAlphas[i]).setDuration(duration).setInterpolator(mZoomInInter🔵
2291 anim.play(alphaAnim);
2292 }
2293 if ((mOldBackgroundAlphas[i] != 0) || (mNewBackgroundAlphas[i] != 0)) {
2294 ValueAnimator bgAnim = LauncherAnimUtils.ofFloat(cl, 0.0F, 1.0F);
2295 bgAnim.setInterpolator(mZoomInInterpolator);
2296 bgAnim.setDuration(duration);
2297 bgAnim.addUpdateListener(new LauncherAnimatorUpdateListener() {
2298 public void onAnimationUpdate(float a, float b) {
2299 cl.setBackgroundAlpha((a * mOldBackgroundAlphas[i]) + (b * mNewBackground🔵
2300 }
2301 });
2302 anim.play(bgAnim);
2303 }
2304 }
2305 }
2306 Animator pageIndicatorAlpha = null;
2307 if (pageIndicator != null) {
2308 pageIndicatorAlpha = new LauncherViewPropertyAnimator(pageIndicator).alpha(finalHotseatAn🔵
2309 pageIndicatorAlpha.addListener(new AlphaUpdateListener(pageIndicator));
2310 } else {
2311 // create a dummy animation so we don't need to do null checks later
2312 pageIndicatorAlpha = ValueAnimator.ofFloat(0, 0);
2313 }
2314 Animator hotseatAlpha = new LauncherViewPropertyAnimator(hotseat).alpha(finalHotseatAndPageIn🔵
2315 hotseatAlpha.addListener(new AlphaUpdateListener(hotseat));
2316 Animator searchBarAlpha = new LauncherViewPropertyAnimator(searchBar).alpha(finalSearchBarAlp🔵
2317 searchBarAlpha.addListener(new AlphaUpdateListener(searchBar));
2318 Animator overviewPanelAlpha = new LauncherViewPropertyAnimator(overviewPanel).alpha(finalOver🔵
2319 overviewPanelAlpha.addListener(new AlphaUpdateListener(overviewPanel));
2320 // For animation optimations, we may need to provide the Launcher transition
2321 // with a set of views on which to force build layers in certain scenarios.
2322 hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2323 searchBar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2324 overviewPanel.setLayerType(View.LAYER_TYPE_HARDWARE, null);
2325 if (layerViews != null) {
2326 layerViews.add(hotseat);
2327 layerViews.add(searchBar);
2328 layerViews.add(overviewPanel);
2329 }
2330 if (workspaceToOverview) {
2331 pageIndicatorAlpha.setInterpolator(new DecelerateInterpolator(2));
2332 hotseatAlpha.setInterpolator(new DecelerateInterpolator(2));
2333 overviewPanelAlpha.setInterpolator(null);
2334 } else if (overviewToWorkspace) {
2335 pageIndicatorAlpha.setInterpolator(null);
2336 hotseatAlpha.setInterpolator(null);
2337 overviewPanelAlpha.setInterpolator(new DecelerateInterpolator(2));
2338 }
2339 overviewPanelAlpha.setDuration(duration);
2340 pageIndicatorAlpha.setDuration(duration);
2341 hotseatAlpha.setDuration(duration);
2342 searchBarAlpha.setDuration(duration);
2343 anim.play(overviewPanelAlpha);
2344 anim.play(hotseatAlpha);
2345 anim.play(searchBarAlpha);
2346 anim.play(pageIndicatorAlpha);
2347 anim.setStartDelay(delay);
2348 } else {
2349 overviewPanel.setAlpha(finalOverviewPanelAlpha);
2350 AlphaUpdateListener.updateVisibility(overviewPanel);
2351 hotseat.setAlpha(finalHotseatAndPageIndicatorAlpha);
2352 AlphaUpdateListener.updateVisibility(hotseat);
2353 if (pageIndicator != null) {
2354 pageIndicator.setAlpha(finalHotseatAndPageIndicatorAlpha);
2355 AlphaUpdateListener.updateVisibility(pageIndicator);
2356 }
2357 searchBar.setAlpha(finalSearchBarAlpha);
2358 AlphaUpdateListener.updateVisibility(searchBar);
2359 updateCustomContentVisibility();
2360 setScaleX(mNewScale);
2361 setScaleY(mNewScale);
2362 setTranslationY(finalWorkspaceTranslationY);
2363 }
2364 mLauncher.updateVoiceButtonProxyVisible(false);
2365 if (stateIsNormal) {
2366 animateBackgroundGradient(0.0F, animated);
2367 } else {
2368 animateBackgroundGradient(getResources().getInteger(R.integer.config_workspaceScrimAlpha) / 1🔵
2369 }
2370 return anim;
2371 }
2372
2373 static class AlphaUpdateListener implements AnimatorUpdateListener , AnimatorListener {
2374 View view;
2375
2376 public AlphaUpdateListener(View v) {
2377 view = v;
2378 }
2379
2380 @Override
2381 public void onAnimationUpdate(ValueAnimator arg0) {
2382 updateVisibility(view);
2383 }
2384
2385 public static void updateVisibility(View view) {
2386 // We want to avoid the extra layout pass by setting the views to GONE unless
2387 // accessibility is on, in which case not setting them to GONE causes a glitch.
2388 int invisibleState = (sAccessibilityEnabled) ? GONE : INVISIBLE;
2389 if ((view.getAlpha() < ALPHA_CUTOFF_THRESHOLD) && (view.getVisibility() != invisibleState)) {
2390 view.setVisibility(invisibleState);
2391 } else if ((view.getAlpha() > ALPHA_CUTOFF_THRESHOLD) && (view.getVisibility() != VISIBLE)) {
2392 view.setVisibility(VISIBLE);
2393 }
2394 }
2395
2396 @Override
2397 public void onAnimationCancel(Animator arg0) {
2398 }
2399
2400 @Override
2401 public void onAnimationEnd(Animator arg0) {
2402 updateVisibility(view);
2403 }
2404
2405 @Override
2406 public void onAnimationRepeat(Animator arg0) {
2407 }
2408
2409 @Override
2410 public void onAnimationStart(Animator arg0) {
2411 // We want the views to be visible for animation, so fade-in/out is visible
2412 view.setVisibility(VISIBLE);
2413 }
2414 }
2415
2416 @Override
2417 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
2418 onTransitionPrepare();
2419 }
2420
2421 @Override
2422 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
2423 }
2424
2425 @Override
2426 public void onLauncherTransitionStep(Launcher l, float t) {
2427 mTransitionProgress = t;
2428 }
2429
2430 @Override
2431 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
2432 onTransitionEnd();
2433 }
2434
2435 private void onTransitionPrepare() {
2436 mIsSwitchingState = true;
2437
2438 // Invalidate here to ensure that the pages are rendered during the state change transition.
2439 invalidate();
2440
2441 updateChildrenLayersEnabled(false);
2442 hideCustomContentIfNecessary();
2443 }
2444
2445 void updateCustomContentVisibility() {
2446 int visibility = mState == Workspace.State.NORMAL ? VISIBLE : INVISIBLE;
2447 if (hasCustomContent()) {
2448 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(visibility);
2449 }
2450 }
2451
2452 void showCustomContentIfNecessary() {
2453 boolean show = mState == Workspace.State.NORMAL;
2454 if (show && hasCustomContent()) {
2455 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(VISIBLE);
2456 }
2457 }
2458
2459 void hideCustomContentIfNecessary() {
2460 boolean hide = mState != Workspace.State.NORMAL;
2461 if (hide && hasCustomContent()) {
2462 disableLayoutTransitions();
2463 mWorkspaceScreens.get(CUSTOM_CONTENT_SCREEN_ID).setVisibility(INVISIBLE);
2464 enableLayoutTransitions();
2465 }
2466 }
2467
2468 private void onTransitionEnd() {
2469 mIsSwitchingState = false;
2470 updateChildrenLayersEnabled(false);
2471 showCustomContentIfNecessary();
2472 }
2473
2474 @Override
2475 public View getContent() {
2476 return this;
2477 }
2478
2479 /**
2480 * Draw the View v into the given Canvas.
2481 *
2482 * @param v the view to draw
2483 * @param destCanvas the canvas to draw on
2484 * @param padding the horizontal and vertical padding to use when drawing
2485 */
2486 private static void drawDragView(View v, Canvas destCanvas, int padding) {
2487 final Rect clipRect = sTempRect;
2488 v.getDrawingRect(clipRect);
2489 boolean textVisible = false;
2490 destCanvas.save();
2491 if (v instanceof TextView) {
2492 Drawable d = ((TextView) (v)).getCompoundDrawables()[1];
2493 Rect bounds = getDrawableBounds(d);
2494 clipRect.set(0, 0, bounds.width() + padding, bounds.height() + padding);
2495 destCanvas.translate((padding / 2) - bounds.left, (padding / 2) - bounds.top);
2496 d.draw(destCanvas);
2497 } else {
2498 if (v instanceof FolderIcon) {
2499 // For FolderIcons the text can bleed into the icon area, and so we need to
2500 // hide the text completely (which can't be achieved by clipping).
2501 if (((FolderIcon) (v)).getTextVisible()) {
2502 ((FolderIcon) (v)).setTextVisible(false);
2503 textVisible = true;
2504 }
2505 }
2506 destCanvas.translate((-v.getScrollX()) + (padding / 2), (-v.getScrollY()) + (padding / 2));
2507 destCanvas.clipRect(clipRect, Op.REPLACE);
2508 v.draw(destCanvas);
2509 // Restore text visibility of FolderIcon if necessary
2510 if (textVisible) {
2511 ((FolderIcon) (v)).setTextVisible(true);
2512 }
2513 }
2514 destCanvas.restore();
2515 }
2516
2517 /**
2518 * Returns a new bitmap to show when the given View is being dragged around.
2519 * Responsibility for the bitmap is transferred to the caller.
2520 *
2521 * @param expectedPadding
2522 * padding to add to the drag view. If a different padding was used
2523 * its value will be changed
2524 */
2525 public Bitmap createDragBitmap(View v, AtomicInteger expectedPadding) {
2526 Bitmap b;
2527 int padding = expectedPadding.get();
2528 if (v instanceof TextView) {
2529 Drawable d = ((TextView) (v)).getCompoundDrawables()[1];
2530 Rect bounds = getDrawableBounds(d);
2531 b = Bitmap.createBitmap(bounds.width() + padding, bounds.height() + padding, Bitmap.Config.AR🔵
2532 expectedPadding.set((padding - bounds.left) - bounds.top);
2533 } else {
2534 b = Bitmap.createBitmap(v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8🔵
2535 }
2536 mCanvas.setBitmap(b);
2537 drawDragView(v, mCanvas, padding);
2538 mCanvas.setBitmap(null);
2539 return b;
2540 }
2541
2542 /**
2543 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2544 * Responsibility for the bitmap is transferred to the caller.
2545 */
2546 private Bitmap createDragOutline(View v, int padding) {
2547 final int outlineColor = getResources().getColor(R.color.outline_color);
2548 final Bitmap b = Bitmap.createBitmap(v.getWidth() + padding, v.getHeight() + padding, Bitmap.Conf🔵
2549 mCanvas.setBitmap(b);
2550 drawDragView(v, mCanvas, padding);
2551 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor);
2552 mCanvas.setBitmap(null);
2553 return b;
2554 }
2555
2556 /**
2557 * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
2558 * Responsibility for the bitmap is transferred to the caller.
2559 */
2560 private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h, boolean clipAlpha) {
2561 final int outlineColor = getResources().getColor(R.color.outline_color);
2562 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
2563 mCanvas.setBitmap(b);
2564 Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
2565 float scaleFactor = Math.min((w - padding) / ((float) (orig.getWidth())), (h - padding) / ((float🔵
2566 int scaledWidth = ((int) (scaleFactor * orig.getWidth()));
2567 int scaledHeight = ((int) (scaleFactor * orig.getHeight()));
2568 Rect dst = new Rect(0, 0, scaledWidth, scaledHeight);
2569 // center the image
2570 dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
2571 mCanvas.drawBitmap(orig, src, dst, null);
2572 mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor, clipAlpha);
2573 mCanvas.setBitmap(null);
2574 return b;
2575 }
2576
2577 void startDrag(CellLayout.CellInfo cellInfo) {
2578 View child = cellInfo.cell;
2579 // Make sure the drag was started by a long press as opposed to a long click.
2580 if (!child.isInTouchMode()) {
2581 return;
2582 }
2583 mDragInfo = cellInfo;
2584 child.setVisibility(INVISIBLE);
2585 CellLayout layout = ((CellLayout) (child.getParent().getParent()));
2586 layout.prepareChildForDrag(child);
2587 beginDragShared(child, this);
2588 }
2589
2590 public void beginDragShared(View child, DragSource source) {
2591 child.clearFocus();
2592 child.setPressed(false);
2593 // The outline is used to visualize where the item will land if dropped
2594 mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
2595 mLauncher.onDragStarted(child);
2596 // The drag bitmap follows the touch point around on the screen
2597 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2598 final Bitmap b = createDragBitmap(child, padding);
2599 final int bmpWidth = b.getWidth();
2600 final int bmpHeight = b.getHeight();
2601 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
2602 int dragLayerX = Math.round(mTempXY[0] - ((bmpWidth - (scale * child.getWidth())) / 2));
2603 int dragLayerY = Math.round((mTempXY[1] - ((bmpHeight - (scale * bmpHeight)) / 2)) - (padding.get🔵
2604 LauncherAppState app = LauncherAppState.getInstance();
2605 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2606 Point dragVisualizeOffset = null;
2607 Rect dragRect = null;
2608 if (child instanceof BubbleTextView) {
2609 int iconSize = grid.iconSizePx;
2610 int top = child.getPaddingTop();
2611 int left = (bmpWidth - iconSize) / 2;
2612 int right = left + iconSize;
2613 int bottom = top + iconSize;
2614 dragLayerY += top;
2615 // Note: The drag region is used to calculate drag layer offsets, but the
2616 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2617 dragVisualizeOffset = new Point((-padding.get()) / 2, padding.get() / 2);
2618 dragRect = new Rect(left, top, right, bottom);
2619 } else if (child instanceof FolderIcon) {
2620 int previewSize = grid.folderIconSizePx;
2621 dragRect = new Rect(0, child.getPaddingTop(), child.getWidth(), previewSize);
2622 }
2623 // Clear the pressed state if necessary
2624 if (child instanceof BubbleTextView) {
2625 BubbleTextView icon = ((BubbleTextView) (child));
2626 icon.clearPressedBackground();
2627 }
2628 if ((child.getTag() == null) || (!(child.getTag() instanceof ItemInfo))) {
2629 String msg = ((("Drag started with a view that has no tag set. This " + ("will cause a crash 🔵
2630 throw new IllegalStateException(msg);
2631 }
2632 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), DragCo🔵
2633 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2634 if (child.getParent() instanceof ShortcutAndWidgetContainer) {
2635 mDragSourceInternal = ((ShortcutAndWidgetContainer) (child.getParent()));
2636 }
2637 b.recycle();
2638 }
2639
2640 public void beginExternalDragShared(View child, DragSource source) {
2641 LauncherAppState app = LauncherAppState.getInstance();
2642 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
2643 int iconSize = grid.iconSizePx;
2644 // Notify launcher of drag start
2645 mLauncher.onDragStarted(child);
2646 // Compose a new drag bitmap that is of the icon size
2647 AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
2648 final Bitmap tmpB = createDragBitmap(child, padding);
2649 Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
2650 Paint p = new Paint();
2651 p.setFilterBitmap(true);
2652 mCanvas.setBitmap(b);
2653 mCanvas.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()), new Rect(0, 0, iconSi🔵
2654 mCanvas.setBitmap(null);
2655 // Find the child's location on the screen
2656 int bmpWidth = tmpB.getWidth();
2657 float iconScale = ((float) (bmpWidth)) / iconSize;
2658 float scale = mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY) * iconScale;
2659 int dragLayerX = Math.round(mTempXY[0] - ((bmpWidth - (scale * child.getWidth())) / 2));
2660 int dragLayerY = Math.round(mTempXY[1]);
2661 // Note: The drag region is used to calculate drag layer offsets, but the
2662 // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
2663 Point dragVisualizeOffset = new Point((-padding.get()) / 2, padding.get() / 2);
2664 Rect dragRect = new Rect(0, 0, iconSize, iconSize);
2665 if ((child.getTag() == null) || (!(child.getTag() instanceof ItemInfo))) {
2666 String msg = ((("Drag started with a view that has no tag set. This " + ("will cause a crash 🔵
2667 throw new IllegalStateException(msg);
2668 }
2669 // Start the drag
2670 DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), DragCo🔵
2671 dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor());
2672 // Recycle temporary bitmaps
2673 tmpB.recycle();
2674 }
2675
2676 void addApplicationShortcut(ShortcutInfo info, CellLayout target, long container, long screenId,
2677 int cellX, int cellY, boolean insertAtFirst, int intersectX, int intersectY) {
2678 View view = mLauncher.createShortcut(R.layout.application, target, (ShortcutInfo) info);
2679
2680 final int[] cellXY = new int[2];
2681 target.findCellForSpanThatIntersects(cellXY, 1, 1, intersectX, intersectY);
2682 addInScreen(view, container, screenId, cellXY[0], cellXY[1], 1, 1, insertAtFirst);
2683
2684 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, cellXY[0],
2685 cellXY[1]);
2686 }
2687
2688 public boolean transitionStateShouldAllowDrop() {
2689 return ((!isSwitchingState()) || (mTransitionProgress > 0.5F)) && ((mState == State.NORMAL) || (m🔵
2690 }
2691
2692 /**
2693 * {@inheritDoc}
2694 */
2695 public boolean acceptDrop(DragObject d) {
2696 // If it's an external drop (e.g. from All Apps), check if it should be accepted
2697 CellLayout dropTargetLayout = mDropToLayout;
2698 if (d.dragSource != this) {
2699 // Don't accept the drop if we're not over a screen at time of drop
2700 if (dropTargetLayout == null) {
2701 return false;
2702 }
2703 if (!transitionStateShouldAllowDrop()) {
2704 return false;
2705 }
2706 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, m🔵
2707 // We want the point to be mapped to the dragTarget.
2708 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2709 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2710 } else {
2711 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2712 }
2713 int spanX = 1;
2714 int spanY = 1;
2715 if (mDragInfo != null) {
2716 final CellLayout.CellInfo dragCellInfo = mDragInfo;
2717 spanX = dragCellInfo.spanX;
2718 spanY = dragCellInfo.spanY;
2719 } else {
2720 final ItemInfo dragInfo = ((ItemInfo) (d.dragInfo));
2721 spanX = dragInfo.spanX;
2722 spanY = dragInfo.spanY;
2723 }
2724 int minSpanX = spanX;
2725 int minSpanY = spanY;
2726 if (d.dragInfo instanceof PendingAddWidgetInfo) {
2727 minSpanX = ((PendingAddWidgetInfo) (d.dragInfo)).minSpanX;
2728 minSpanY = ((PendingAddWidgetInfo) (d.dragInfo)).minSpanY;
2729 }
2730 mTargetCell = findNearestArea(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisualCent🔵
2731 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisu🔵
2732 if (mCreateUserFolderOnDrop && willCreateUserFolder(((ItemInfo) (d.dragInfo)), dropTargetLayo🔵
2733 return true;
2734 }
2735 if (mAddToExistingFolderOnDrop && willAddToExistingUserFolder(((ItemInfo) (d.dragInfo)), drop🔵
2736 return true;
2737 }
2738 int[] resultSpan = new int[2];
2739 mTargetCell = dropTargetLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mDr🔵
2740 boolean foundCell = (mTargetCell[0] >= 0) && (mTargetCell[1] >= 0);
2741 // Don't accept the drop if there's no room for the item
2742 if (!foundCell) {
2743 // Don't show the message if we are dropping on the AllApps button and the hotseat
2744 // is full
2745 boolean isHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2746 if ((mTargetCell != null) && isHotseat) {
2747 Hotseat hotseat = mLauncher.getHotseat();
2748 if (hotseat.isAllAppsButtonRank(hotseat.getOrderInHotseat(mTargetCell[0], mTargetCell🔵
2749 return false;
2750 }
2751 }
2752 mLauncher.showOutOfSpaceMessage(isHotseat);
2753 return false;
2754 }
2755 }
2756 long screenId = getIdForScreen(dropTargetLayout);
2757 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
2758 commitExtraEmptyScreen();
2759 }
2760 return true;
2761 }
2762
2763 boolean willCreateUserFolder(ItemInfo info, CellLayout target, int[] targetCell, float
2764 distance, boolean considerTimeout) {
2765 if (distance > mMaxDistanceForFolderCreation) return false;
2766 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2767
2768 if (dropOverView != null) {
2769 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2770 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2771 return false;
2772 }
2773 }
2774
2775 boolean hasntMoved = false;
2776 if (mDragInfo != null) {
2777 hasntMoved = dropOverView == mDragInfo.cell;
2778 }
2779
2780 if (dropOverView == null || hasntMoved || (considerTimeout && !mCreateUserFolderOnDrop)) {
2781 return false;
2782 }
2783
2784 boolean aboveShortcut = (dropOverView.getTag() instanceof ShortcutInfo);
2785 boolean willBecomeShortcut =
2786 (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
2787 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT);
2788
2789 return (aboveShortcut && willBecomeShortcut);
2790 }
2791
2792 boolean willAddToExistingUserFolder(Object dragInfo, CellLayout target, int[] targetCell,
2793 float distance) {
2794 if (distance > mMaxDistanceForFolderCreation) return false;
2795 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2796
2797 if (dropOverView != null) {
2798 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) dropOverView.getLayoutParams();
2799 if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.tmpCellY)) {
2800 return false;
2801 }
2802 }
2803
2804 if (dropOverView instanceof FolderIcon) {
2805 FolderIcon fi = (FolderIcon) dropOverView;
2806 if (fi.acceptDrop(dragInfo)) {
2807 return true;
2808 }
2809 }
2810 return false;
2811 }
2812
2813 boolean createUserFolderIfNecessary(View newView, long container, CellLayout target,
2814 int[] targetCell, float distance, boolean external, DragView dragView,
2815 Runnable postAnimationRunnable) {
2816 if (distance > mMaxDistanceForFolderCreation) return false;
2817 View v = target.getChildAt(targetCell[0], targetCell[1]);
2818
2819 boolean hasntMoved = false;
2820 if (mDragInfo != null) {
2821 CellLayout cellParent = getParentCellLayoutForView(mDragInfo.cell);
2822 hasntMoved = (mDragInfo.cellX == targetCell[0] &&
2823 mDragInfo.cellY == targetCell[1]) && (cellParent == target);
2824 }
2825
2826 if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
2827 mCreateUserFolderOnDrop = false;
2828 final long screenId = (targetCell == null) ? mDragInfo.screenId : getIdForScreen(target);
2829
2830 boolean aboveShortcut = (v.getTag() instanceof ShortcutInfo);
2831 boolean willBecomeShortcut = (newView.getTag() instanceof ShortcutInfo);
2832
2833 if (aboveShortcut && willBecomeShortcut) {
2834 ShortcutInfo sourceInfo = (ShortcutInfo) newView.getTag();
2835 ShortcutInfo destInfo = (ShortcutInfo) v.getTag();
2836 // if the drag started here, we need to remove it from the workspace
2837 if (!external) {
2838 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2839 }
2840
2841 Rect folderLocation = new Rect();
2842 float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
2843 target.removeView(v);
2844
2845 FolderIcon fi =
2846 mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
2847 destInfo.cellX = -1;
2848 destInfo.cellY = -1;
2849 sourceInfo.cellX = -1;
2850 sourceInfo.cellY = -1;
2851
2852 // If the dragView is null, we can't animate
2853 boolean animate = dragView != null;
2854 if (animate) {
2855 fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale,
2856 postAnimationRunnable);
2857 } else {
2858 fi.addItem(destInfo);
2859 fi.addItem(sourceInfo);
2860 }
2861 return true;
2862 }
2863 return false;
2864 }
2865
2866 boolean addToExistingFolderIfNecessary(View newView, CellLayout target, int[] targetCell,
2867 float distance, DragObject d, boolean external) {
2868 if (distance > mMaxDistanceForFolderCreation) return false;
2869
2870 View dropOverView = target.getChildAt(targetCell[0], targetCell[1]);
2871 if (!mAddToExistingFolderOnDrop) return false;
2872 mAddToExistingFolderOnDrop = false;
2873
2874 if (dropOverView instanceof FolderIcon) {
2875 FolderIcon fi = (FolderIcon) dropOverView;
2876 if (fi.acceptDrop(d.dragInfo)) {
2877 fi.onDrop(d);
2878
2879 // if the drag started here, we need to remove it from the workspace
2880 if (!external) {
2881 getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
2882 }
2883 return true;
2884 }
2885 }
2886 return false;
2887 }
2888
2889 public void onDrop(final DragObject d) {
2890 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, mDrag🔵
2891 CellLayout dropTargetLayout = mDropToLayout;
2892 // We want the point to be mapped to the dragTarget.
2893 if (dropTargetLayout != null) {
2894 if (mLauncher.isHotseatLayout(dropTargetLayout)) {
2895 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
2896 } else {
2897 mapPointFromSelfToChild(dropTargetLayout, mDragViewVisualCenter, null);
2898 }
2899 }
2900 int snapScreen = -1;
2901 boolean resizeOnDrop = false;
2902 if (d.dragSource != this) {
2903 final int[] touchXY = new int[]{ ((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisualC🔵
2904 onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
2905 } else if (mDragInfo != null) {
2906 final View cell = mDragInfo.cell;
2907 Runnable resizeRunnable = null;
2908 if ((dropTargetLayout != null) && (!d.cancelled)) {
2909 // Move internally
2910 boolean hasMovedLayouts = getParentCellLayoutForView(cell) != dropTargetLayout;
2911 boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
2912 long container = (hasMovedIntoHotseat) ? Favorites.CONTAINER_HOTSEAT : Favorites.CONTAINE🔵
2913 long screenId = (mTargetCell[0] < 0) ? mDragInfo.screenId : getIdForScreen(dropTargetLayo🔵
2914 int spanX = (mDragInfo != null) ? mDragInfo.spanX : 1;
2915 int spanY = (mDragInfo != null) ? mDragInfo.spanY : 1;
2916 // First we find the cell nearest to point at which the item is
2917 // dropped, without any consideration to whether there is an item there.
2918 mTargetCell = findNearestArea(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisual🔵
2919 float distance = dropTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragView🔵
2920 // If the item being dropped is a shortcut and the nearest drop
2921 // cell also contains a shortcut, then create a folder with the two shortcuts.
2922 if ((!mInScrollArea) && createUserFolderIfNecessary(cell, container, dropTargetLayout, mT🔵
2923 return;
2924 }
2925 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, distance, d, fals🔵
2926 return;
2927 }
2928 // Aside from the special case where we're dropping a shortcut onto a shortcut,
2929 // we need to find the nearest cell location that is vacant
2930 ItemInfo item = ((ItemInfo) (d.dragInfo));
2931 int minSpanX = item.spanX;
2932 int minSpanY = item.spanY;
2933 if ((item.minSpanX > 0) && (item.minSpanY > 0)) {
2934 minSpanX = item.minSpanX;
2935 minSpanY = item.minSpanY;
2936 }
2937 int[] resultSpan = new int[2];
2938 mTargetCell = dropTargetLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) 🔵
2939 boolean foundCell = (mTargetCell[0] >= 0) && (mTargetCell[1] >= 0);
2940 // if the widget resizes on drop
2941 if ((foundCell && (cell instanceof AppWidgetHostView)) && ((resultSpan[0] != item.spanX) 🔵
2942 resizeOnDrop = true;
2943 item.spanX = resultSpan[0];
2944 item.spanY = resultSpan[1];
2945 AppWidgetHostView awhv = ((AppWidgetHostView) (cell));
2946 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, resultSpan[0], resultSpa🔵
2947 }
2948 if ((getScreenIdForPageIndex(mCurrentPage) != screenId) && (!hasMovedIntoHotseat)) {
2949 snapScreen = getPageIndexForScreenId(screenId);
2950 snapToPage(snapScreen);
2951 }
2952 if (foundCell) {
2953 final ItemInfo info = ((ItemInfo) (cell.getTag()));
2954 if (hasMovedLayouts) {
2955 // Reparent the view
2956 CellLayout parentCell = getParentCellLayoutForView(cell);
2957 if (parentCell != null) {
2958 parentCell.removeView(cell);
2959 } else if (LauncherAppState.isDogfoodBuild()) {
2960 throw new NullPointerException("mDragInfo.cell has null parent");
2961 }
2962 addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX🔵
2963 }
2964 // update the item's position after drop
2965 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) (cell.getLayoutParams()));
2966 lp.cellX = lp.tmpCellX = mTargetCell[0];
2967 lp.cellY = lp.tmpCellY = mTargetCell[1];
2968 lp.cellHSpan = item.spanX;
2969 lp.cellVSpan = item.spanY;
2970 lp.isLockedToGrid = true;
2971 if ((container != Favorites.CONTAINER_HOTSEAT) && (cell instanceof LauncherAppWidgetH🔵
2972 final CellLayout cellLayout = dropTargetLayout;
2973 // We post this call so that the widget has a chance to be placed
2974 // in its final location
2975 final LauncherAppWidgetHostView hostView = ((LauncherAppWidgetHostView) (cell));
2976 AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
2977 if ((pinfo != null) && (pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE)) {
2978 final Runnable addResizeFrame = new Runnable() {
2979 public void run() {
2980 DragLayer dragLayer = mLauncher.getDragLayer();
2981 dragLayer.addResizeFrame(info, hostView, cellLayout);
2982 }
2983 };
2984 resizeRunnable = new Runnable() {
2985 public void run() {
2986 if (!isPageMoving()) {
2987 addResizeFrame.run();
2988 } else {
2989 mDelayedResizeRunnable = addResizeFrame;
2990 }
2991 }
2992 };
2993 }
2994 }
2995 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, lp.cellX, lp🔵
2996 } else {
2997 // If we can't find a drop location, we return the item to its original position
2998 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) (cell.getLayoutParams()));
2999 mTargetCell[0] = lp.cellX;
3000 mTargetCell[1] = lp.cellY;
3001 CellLayout layout = ((CellLayout) (cell.getParent().getParent()));
3002 layout.markCellsAsOccupiedForView(cell);
3003 }
3004 }
3005 final CellLayout parent = ((CellLayout) (cell.getParent().getParent()));
3006 final Runnable finalResizeRunnable = resizeRunnable;
3007 // Prepare it to be animated into its new position
3008 // This must be called after the view has been re-parented
3009 final Runnable onCompleteRunnable = new Runnable() {
3010 @Override
3011 public void run() {
3012 mAnimatingViewIntoPlace = false;
3013 updateChildrenLayersEnabled(false);
3014 if (finalResizeRunnable != null) {
3015 finalResizeRunnable.run();
3016 }
3017 }
3018 };
3019 mAnimatingViewIntoPlace = true;
3020 if (d.dragView.hasDrawn()) {
3021 final ItemInfo info = ((ItemInfo) (cell.getTag()));
3022 if (info.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
3023 int animationType = (resizeOnDrop) ? ANIMATE_INTO_POSITION_AND_RESIZE : ANIMATE_INTO_🔵
3024 animateWidgetDrop(info, parent, d.dragView, onCompleteRunnable, animationType, cell, 🔵
3025 } else {
3026 int duration = (snapScreen < 0) ? -1 : ADJACENT_SCREEN_DROP_DURATION;
3027 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration, onComple🔵
3028 }
3029 } else {
3030 d.deferDragViewCleanupPostAnimation = false;
3031 cell.setVisibility(VISIBLE);
3032 }
3033 parent.onDropChild(cell);
3034 }
3035 }
3036
3037 public void setFinalScrollForPageChange(int pageIndex) {
3038 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3039 if (cl != null) {
3040 mSavedScrollX = getScrollX();
3041 mSavedTranslationX = cl.getTranslationX();
3042 mSavedRotationY = cl.getRotationY();
3043 final int newX = getScrollForPage(pageIndex);
3044 setScrollX(newX);
3045 cl.setTranslationX(0f);
3046 cl.setRotationY(0f);
3047 }
3048 }
3049
3050 public void resetFinalScrollForPageChange(int pageIndex) {
3051 if (pageIndex >= 0) {
3052 CellLayout cl = (CellLayout) getChildAt(pageIndex);
3053 setScrollX(mSavedScrollX);
3054 cl.setTranslationX(mSavedTranslationX);
3055 cl.setRotationY(mSavedRotationY);
3056 }
3057 }
3058
3059 public void getViewLocationRelativeToSelf(View v, int[] location) {
3060 getLocationInWindow(location);
3061 int x = location[0];
3062 int y = location[1];
3063
3064 v.getLocationInWindow(location);
3065 int vX = location[0];
3066 int vY = location[1];
3067
3068 location[0] = vX - x;
3069 location[1] = vY - y;
3070 }
3071
3072 public void onDragEnter(DragObject d) {
3073 mDragEnforcer.onDragEnter();
3074 mCreateUserFolderOnDrop = false;
3075 mAddToExistingFolderOnDrop = false;
3076 mDropToLayout = null;
3077 CellLayout layout = getCurrentDropLayout();
3078 setCurrentDropLayout(layout);
3079 setCurrentDragOverlappingLayout(layout);
3080 if (!workspaceInModalState()) {
3081 mLauncher.getDragLayer().showPageHints();
3082 }
3083 }
3084
3085 /** Return a rect that has the cellWidth/cellHeight (left, top), and
3086 * widthGap/heightGap (right, bottom) */
3087 static Rect getCellLayoutMetrics(Launcher launcher, int orientation) {
3088 LauncherAppState app = LauncherAppState.getInstance();
3089 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3090 Display display = launcher.getWindowManager().getDefaultDisplay();
3091 Point smallestSize = new Point();
3092 Point largestSize = new Point();
3093 display.getCurrentSizeRange(smallestSize, largestSize);
3094 int countX = ((int) (grid.numColumns));
3095 int countY = ((int) (grid.numRows));
3096 if (orientation == CellLayout.LANDSCAPE) {
3097 if (mLandscapeCellLayoutMetrics == null) {
3098 Rect padding = grid.getWorkspacePadding(CellLayout.LANDSCAPE);
3099 int width = (largestSize.x - padding.left) - padding.right;
3100 int height = (smallestSize.y - padding.top) - padding.bottom;
3101 mLandscapeCellLayoutMetrics = new Rect();
3102 mLandscapeCellLayoutMetrics.set(grid.calculateCellWidth(width, countX), grid.calculateCel🔵
3103 }
3104 return mLandscapeCellLayoutMetrics;
3105 } else if (orientation == CellLayout.PORTRAIT) {
3106 if (mPortraitCellLayoutMetrics == null) {
3107 Rect padding = grid.getWorkspacePadding(CellLayout.PORTRAIT);
3108 int width = (smallestSize.x - padding.left) - padding.right;
3109 int height = (largestSize.y - padding.top) - padding.bottom;
3110 mPortraitCellLayoutMetrics = new Rect();
3111 mPortraitCellLayoutMetrics.set(grid.calculateCellWidth(width, countX), grid.calculateCell🔵
3112 }
3113 return mPortraitCellLayoutMetrics;
3114 }
3115 return null;
3116 }
3117
3118 public void onDragExit(DragObject d) {
3119 mDragEnforcer.onDragExit();
3120 // Here we store the final page that will be dropped to, if the workspace in fact
3121 // receives the drop
3122 if (mInScrollArea) {
3123 if (isPageMoving()) {
3124 // If the user drops while the page is scrolling, we should use that page as the
3125 // destination instead of the page that is being hovered over.
3126 mDropToLayout = ((CellLayout) (getPageAt(getNextPage())));
3127 } else {
3128 mDropToLayout = mDragOverlappingLayout;
3129 }
3130 } else {
3131 mDropToLayout = mDragTargetLayout;
3132 }
3133 if (mDragMode == DRAG_MODE_CREATE_FOLDER) {
3134 mCreateUserFolderOnDrop = true;
3135 } else if (mDragMode == DRAG_MODE_ADD_TO_FOLDER) {
3136 mAddToExistingFolderOnDrop = true;
3137 }
3138 // Reset the scroll area and previous drag target
3139 onResetScrollArea();
3140 setCurrentDropLayout(null);
3141 setCurrentDragOverlappingLayout(null);
3142 mSpringLoadedDragController.cancel();
3143 if (!mIsPageMoving) {
3144 hideOutlines();
3145 }
3146 mLauncher.getDragLayer().hidePageHints();
3147 }
3148
3149 void setCurrentDropLayout(CellLayout layout) {
3150 if (mDragTargetLayout != null) {
3151 mDragTargetLayout.revertTempState();
3152 mDragTargetLayout.onDragExit();
3153 }
3154 mDragTargetLayout = layout;
3155 if (mDragTargetLayout != null) {
3156 mDragTargetLayout.onDragEnter();
3157 }
3158 cleanupReorder(true);
3159 cleanupFolderCreation();
3160 setCurrentDropOverCell(-1, -1);
3161 }
3162
3163 void setCurrentDragOverlappingLayout(CellLayout layout) {
3164 if (mDragOverlappingLayout != null) {
3165 mDragOverlappingLayout.setIsDragOverlapping(false);
3166 }
3167 mDragOverlappingLayout = layout;
3168 if (mDragOverlappingLayout != null) {
3169 mDragOverlappingLayout.setIsDragOverlapping(true);
3170 }
3171 invalidate();
3172 }
3173
3174 void setCurrentDropOverCell(int x, int y) {
3175 if (x != mDragOverX || y != mDragOverY) {
3176 mDragOverX = x;
3177 mDragOverY = y;
3178 setDragMode(DRAG_MODE_NONE);
3179 }
3180 }
3181
3182 void setDragMode(int dragMode) {
3183 if (dragMode != mDragMode) {
3184 if (dragMode == DRAG_MODE_NONE) {
3185 cleanupAddToFolder();
3186 // We don't want to cancel the re-order alarm every time the target cell changes
3187 // as this feels to slow / unresponsive.
3188 cleanupReorder(false);
3189 cleanupFolderCreation();
3190 } else if (dragMode == DRAG_MODE_ADD_TO_FOLDER) {
3191 cleanupReorder(true);
3192 cleanupFolderCreation();
3193 } else if (dragMode == DRAG_MODE_CREATE_FOLDER) {
3194 cleanupAddToFolder();
3195 cleanupReorder(true);
3196 } else if (dragMode == DRAG_MODE_REORDER) {
3197 cleanupAddToFolder();
3198 cleanupFolderCreation();
3199 }
3200 mDragMode = dragMode;
3201 }
3202 }
3203
3204 private void cleanupFolderCreation() {
3205 if (mDragFolderRingAnimator != null) {
3206 mDragFolderRingAnimator.animateToNaturalState();
3207 mDragFolderRingAnimator = null;
3208 }
3209 mFolderCreationAlarm.setOnAlarmListener(null);
3210 mFolderCreationAlarm.cancelAlarm();
3211 }
3212
3213 private void cleanupAddToFolder() {
3214 if (mDragOverFolderIcon != null) {
3215 mDragOverFolderIcon.onDragExit(null);
3216 mDragOverFolderIcon = null;
3217 }
3218 }
3219
3220 private void cleanupReorder(boolean cancelAlarm) {
3221 // Any pending reorders are canceled
3222 if (cancelAlarm) {
3223 mReorderAlarm.cancelAlarm();
3224 }
3225 mLastReorderX = -1;
3226 mLastReorderY = -1;
3227 }
3228
3229 /*
3230 *
3231 * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
3232 * coordinate space. The argument xy is modified with the return result.
3233 *
3234 * if cachedInverseMatrix is not null, this method will just use that matrix instead of
3235 * computing it itself; we use this to avoid redundant matrix inversions in
3236 * findMatchingPageForDragOver
3237 *
3238 */
3239 void mapPointFromSelfToChild(View v, float[] xy, Matrix cachedInverseMatrix) {
3240 xy[0] = xy[0] - v.getLeft();
3241 xy[1] = xy[1] - v.getTop();
3242 }
3243
3244 boolean isPointInSelfOverHotseat(int x, int y, Rect r) {
3245 if (r == null) {
3246 r = new Rect();
3247 }
3248 mTempPt[0] = x;
3249 mTempPt[1] = y;
3250 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3251
3252 LauncherAppState app = LauncherAppState.getInstance();
3253 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
3254 r = grid.getHotseatRect();
3255 if (r.contains(mTempPt[0], mTempPt[1])) {
3256 return true;
3257 }
3258 return false;
3259 }
3260
3261 void mapPointFromSelfToHotseatLayout(Hotseat hotseat, float[] xy) {
3262 mTempPt[0] = (int) xy[0];
3263 mTempPt[1] = (int) xy[1];
3264 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempPt, true);
3265 mLauncher.getDragLayer().mapCoordInSelfToDescendent(hotseat.getLayout(), mTempPt);
3266
3267 xy[0] = mTempPt[0];
3268 xy[1] = mTempPt[1];
3269 }
3270
3271 /*
3272 *
3273 * Convert the 2D coordinate xy from this CellLayout's coordinate space to
3274 * the parent View's coordinate space. The argument xy is modified with the return result.
3275 *
3276 */
3277 void mapPointFromChildToSelf(View v, float[] xy) {
3278 xy[0] += v.getLeft();
3279 xy[1] += v.getTop();
3280 }
3281
3282 static private float squaredDistance(float[] point1, float[] point2) {
3283 float distanceX = point1[0] - point2[0];
3284 float distanceY = point2[1] - point2[1];
3285 return distanceX * distanceX + distanceY * distanceY;
3286 }
3287
3288 /*
3289 *
3290 * This method returns the CellLayout that is currently being dragged to. In order to drag
3291 * to a CellLayout, either the touch point must be directly over the CellLayout, or as a second
3292 * strategy, we see if the dragView is overlapping any CellLayout and choose the closest one
3293 *
3294 * Return null if no CellLayout is currently being dragged over
3295 *
3296 */
3297 private CellLayout findMatchingPageForDragOver(
3298 DragView dragView, float originX, float originY, boolean exact) {
3299 // We loop through all the screens (ie CellLayouts) and see which ones overlap
3300 // with the item being dragged and then choose the one that's closest to the touch point
3301 final int screenCount = getChildCount();
3302 CellLayout bestMatchingScreen = null;
3303 float smallestDistSoFar = Float.MAX_VALUE;
3304
3305 for (int i = 0; i < screenCount; i++) {
3306 // The custom content screen is not a valid drag over option
3307 if (mScreenOrder.get(i) == CUSTOM_CONTENT_SCREEN_ID) {
3308 continue;
3309 }
3310
3311 CellLayout cl = (CellLayout) getChildAt(i);
3312
3313 final float[] touchXy = {originX, originY};
3314 // Transform the touch coordinates to the CellLayout's local coordinates
3315 // If the touch point is within the bounds of the cell layout, we can return immediately
3316 cl.getMatrix().invert(mTempInverseMatrix);
3317 mapPointFromSelfToChild(cl, touchXy, mTempInverseMatrix);
3318
3319 if (touchXy[0] >= 0 && touchXy[0] <= cl.getWidth() &&
3320 touchXy[1] >= 0 && touchXy[1] <= cl.getHeight()) {
3321 return cl;
3322 }
3323
3324 if (!exact) {
3325 // Get the center of the cell layout in screen coordinates
3326 final float[] cellLayoutCenter = mTempCellLayoutCenterCoordinates;
3327 cellLayoutCenter[0] = cl.getWidth()/2;
3328 cellLayoutCenter[1] = cl.getHeight()/2;
3329 mapPointFromChildToSelf(cl, cellLayoutCenter);
3330
3331 touchXy[0] = originX;
3332 touchXy[1] = originY;
3333
3334 // Calculate the distance between the center of the CellLayout
3335 // and the touch point
3336 float dist = squaredDistance(touchXy, cellLayoutCenter);
3337
3338 if (dist < smallestDistSoFar) {
3339 smallestDistSoFar = dist;
3340 bestMatchingScreen = cl;
3341 }
3342 }
3343 }
3344 return bestMatchingScreen;
3345 }
3346
3347 // This is used to compute the visual center of the dragView. This point is then
3348 // used to visualize drop locations and determine where to drop an item. The idea is that
3349 // the visual center represents the user's interpretation of where the item is, and hence
3350 // is the appropriate point to use when determining drop location.
3351 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset,
3352 DragView dragView, float[] recycle) {
3353 float res[];
3354 if (recycle == null) {
3355 res = new float[2];
3356 } else {
3357 res = recycle;
3358 }
3359
3360 // First off, the drag view has been shifted in a way that is not represented in the
3361 // x and y values or the x/yOffsets. Here we account for that shift.
3362 x += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetX);
3363 y += getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
3364
3365 // These represent the visual top and left of drag view if a dragRect was provided.
3366 // If a dragRect was not provided, then they correspond to the actual view left and
3367 // top, as the dragRect is in that case taken to be the entire dragView.
3368 // R.dimen.dragViewOffsetY.
3369 int left = x - xOffset;
3370 int top = y - yOffset;
3371
3372 // In order to find the visual center, we shift by half the dragRect
3373 res[0] = left + dragView.getDragRegion().width() / 2;
3374 res[1] = top + dragView.getDragRegion().height() / 2;
3375
3376 return res;
3377 }
3378
3379 private boolean isDragWidget(DragObject d) {
3380 return (d.dragInfo instanceof LauncherAppWidgetInfo ||
3381 d.dragInfo instanceof PendingAddWidgetInfo);
3382 }
3383
3384 private boolean isExternalDragWidget(DragObject d) {
3385 return d.dragSource != this && isDragWidget(d);
3386 }
3387
3388 public void onDragOver(DragObject d) {
3389 // Skip drag over events while we are dragging over side pages
3390 if (mInScrollArea || (!transitionStateShouldAllowDrop())) {
3391 return;
3392 }
3393 Rect r = new Rect();
3394 CellLayout layout = null;
3395 ItemInfo item = ((ItemInfo) (d.dragInfo));
3396 if (item == null) {
3397 if (LauncherAppState.isDogfoodBuild()) {
3398 throw new NullPointerException("DragObject has null info");
3399 }
3400 return;
3401 }
3402 // Ensure that we have proper spans for the item that we are dropping
3403 if ((item.spanX < 0) || (item.spanY < 0)) {
3404 throw new RuntimeException("Improper spans found");
3405 }
3406 mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, mDrag🔵
3407 final View child = (mDragInfo == null) ? null : mDragInfo.cell;
3408 // Identify whether we have dragged over a side page
3409 if (workspaceInModalState()) {
3410 if ((mLauncher.getHotseat() != null) && (!isExternalDragWidget(d))) {
3411 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3412 layout = mLauncher.getHotseat().getLayout();
3413 }
3414 }
3415 if (layout == null) {
3416 layout = findMatchingPageForDragOver(d.dragView, d.x, d.y, false);
3417 }
3418 if (layout != mDragTargetLayout) {
3419 setCurrentDropLayout(layout);
3420 setCurrentDragOverlappingLayout(layout);
3421 boolean isInSpringLoadedMode = mState == State.SPRING_LOADED;
3422 if (isInSpringLoadedMode) {
3423 if (mLauncher.isHotseatLayout(layout)) {
3424 mSpringLoadedDragController.cancel();
3425 } else {
3426 mSpringLoadedDragController.setAlarm(mDragTargetLayout);
3427 }
3428 }
3429 }
3430 } else {
3431 // Test to see if we are over the hotseat otherwise just use the current page
3432 if ((mLauncher.getHotseat() != null) && (!isDragWidget(d))) {
3433 if (isPointInSelfOverHotseat(d.x, d.y, r)) {
3434 layout = mLauncher.getHotseat().getLayout();
3435 }
3436 }
3437 if (layout == null) {
3438 layout = getCurrentDropLayout();
3439 }
3440 if (layout != mDragTargetLayout) {
3441 setCurrentDropLayout(layout);
3442 setCurrentDragOverlappingLayout(layout);
3443 }
3444 }
3445 // Handle the drag over
3446 if (mDragTargetLayout != null) {
3447 // We want the point to be mapped to the dragTarget.
3448 if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
3449 mapPointFromSelfToHotseatLayout(mLauncher.getHotseat(), mDragViewVisualCenter);
3450 } else {
3451 mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
3452 }
3453 ItemInfo info = ((ItemInfo) (d.dragInfo));
3454 int minSpanX = item.spanX;
3455 int minSpanY = item.spanY;
3456 if ((item.minSpanX > 0) && (item.minSpanY > 0)) {
3457 minSpanX = item.minSpanX;
3458 minSpanY = item.minSpanY;
3459 }
3460 mTargetCell = findNearestArea(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisualCent🔵
3461 int reorderX = mTargetCell[0];
3462 int reorderY = mTargetCell[1];
3463 setCurrentDropOverCell(mTargetCell[0], mTargetCell[1]);
3464 float targetCellDistance = mDragTargetLayout.getDistanceFromCell(mDragViewVisualCenter[0], mD🔵
3465 final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
3466 manageFolderFeedback(info, mDragTargetLayout, mTargetCell, targetCellDistance, dragOverView);
3467 boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied(((int) (mDragVi🔵
3468 if (!nearestDropOccupied) {
3469 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, ((int) (mDragViewVisualCente🔵
3470 } else if ((((mDragMode == DRAG_MODE_NONE) || (mDragMode == DRAG_MODE_REORDER)) && (!mReorder🔵
3471 int[] resultSpan = new int[2];
3472 mDragTargetLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVis🔵
3473 // Otherwise, if we aren't adding to or creating a folder and there's no pending
3474 // reorder, then we schedule a reorder
3475 ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, minSpanX,🔵
3476 mReorderAlarm.setOnAlarmListener(listener);
3477 mReorderAlarm.setAlarm(REORDER_TIMEOUT);
3478 }
3479 if (((mDragMode == DRAG_MODE_CREATE_FOLDER) || (mDragMode == DRAG_MODE_ADD_TO_FOLDER)) || (!n🔵
3480 if (mDragTargetLayout != null) {
3481 mDragTargetLayout.revertTempState();
3482 }
3483 }
3484 }
3485 }
3486
3487 private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout,
3488 int[] targetCell, float distance, View dragOverView) {
3489 boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance,
3490 false);
3491
3492 if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
3493 !mFolderCreationAlarm.alarmPending()) {
3494 mFolderCreationAlarm.setOnAlarmListener(new
3495 FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]));
3496 mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
3497 return;
3498 }
3499
3500 boolean willAddToFolder =
3501 willAddToExistingUserFolder(info, targetLayout, targetCell, distance);
3502
3503 if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
3504 mDragOverFolderIcon = ((FolderIcon) dragOverView);
3505 mDragOverFolderIcon.onDragEnter(info);
3506 if (targetLayout != null) {
3507 targetLayout.clearDragOutlines();
3508 }
3509 setDragMode(DRAG_MODE_ADD_TO_FOLDER);
3510 return;
3511 }
3512
3513 if (mDragMode == DRAG_MODE_ADD_TO_FOLDER && !willAddToFolder) {
3514 setDragMode(DRAG_MODE_NONE);
3515 }
3516 if (mDragMode == DRAG_MODE_CREATE_FOLDER && !userFolderPending) {
3517 setDragMode(DRAG_MODE_NONE);
3518 }
3519
3520 return;
3521 }
3522
3523 class FolderCreationAlarmListener implements OnAlarmListener {
3524 CellLayout layout;
3525
3526 int cellX;
3527
3528 int cellY;
3529
3530 public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
3531 this.layout = layout;
3532 this.cellX = cellX;
3533 this.cellY = cellY;
3534 }
3535
3536 public void onAlarm(Alarm alarm) {
3537 if (mDragFolderRingAnimator != null) {
3538 // This shouldn't happen ever, but just in case, make sure we clean up the mess.
3539 mDragFolderRingAnimator.animateToNaturalState();
3540 }
3541 mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null);
3542 mDragFolderRingAnimator.setCell(cellX, cellY);
3543 mDragFolderRingAnimator.setCellLayout(layout);
3544 mDragFolderRingAnimator.animateToAcceptState();
3545 layout.showFolderAccept(mDragFolderRingAnimator);
3546 layout.clearDragOutlines();
3547 setDragMode(DRAG_MODE_CREATE_FOLDER);
3548 }
3549 }
3550
3551 class ReorderAlarmListener implements OnAlarmListener {
3552 float[] dragViewCenter;
3553
3554 int minSpanX;
3555
3556 int minSpanY;
3557
3558 int spanX;
3559
3560 int spanY;
3561
3562 DragView dragView;
3563
3564 View child;
3565
3566 public ReorderAlarmListener(float[] dragViewCenter, int minSpanX, int minSpanY, int spanX, int sp🔵
3567 this.dragViewCenter = dragViewCenter;
3568 this.minSpanX = minSpanX;
3569 this.minSpanY = minSpanY;
3570 this.spanX = spanX;
3571 this.spanY = spanY;
3572 this.child = child;
3573 this.dragView = dragView;
3574 }
3575
3576 public void onAlarm(Alarm alarm) {
3577 int[] resultSpan = new int[2];
3578 mTargetCell = findNearestArea(((int) (mDragViewVisualCenter[0])), ((int) (mDragViewVisualCent🔵
3579 mLastReorderX = mTargetCell[0];
3580 mLastReorderY = mTargetCell[1];
3581 mTargetCell = mDragTargetLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mD🔵
3582 if ((mTargetCell[0] < 0) || (mTargetCell[1] < 0)) {
3583 mDragTargetLayout.revertTempState();
3584 } else {
3585 setDragMode(DRAG_MODE_REORDER);
3586 }
3587 boolean resize = (resultSpan[0] != spanX) || (resultSpan[1] != spanY);
3588 mDragTargetLayout.visualizeDropLocation(child, mDragOutline, ((int) (mDragViewVisualCenter[0]🔵
3589 }
3590 }
3591
3592 @Override
3593 public void getHitRectRelativeToDragLayer(Rect outRect) {
3594 // We want the workspace to have the whole area of the display (it will find the correct
3595 // cell layout to drop to in the existing drag/drop logic.
3596 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(this, outRect);
3597 }
3598
3599 /**
3600 * Add the item specified by dragInfo to the given layout.
3601 * @return true if successful
3602 */
3603 public boolean addExternalItemToScreen(ItemInfo dragInfo, CellLayout layout) {
3604 if (layout.findCellForSpan(mTempEstimate, dragInfo.spanX, dragInfo.spanY)) {
3605 onDropExternal(dragInfo.dropPos, (ItemInfo) dragInfo, (CellLayout) layout, false);
3606 return true;
3607 }
3608 mLauncher.showOutOfSpaceMessage(mLauncher.isHotseatLayout(layout));
3609 return false;
3610 }
3611
3612 private void onDropExternal(int[] touchXY, Object dragInfo,
3613 CellLayout cellLayout, boolean insertAtFirst) {
3614 onDropExternal(touchXY, dragInfo, cellLayout, insertAtFirst, null);
3615 }
3616
3617 /**
3618 * Drop an item that didn't originate on one of the workspace screens.
3619 * It may have come from Launcher (e.g. from all apps or customize), or it may have
3620 * come from another app altogether.
3621 *
3622 * NOTE: This can also be called when we are outside of a drag event, when we want
3623 * to add an item to one of the workspace screens.
3624 */
3625 private void onDropExternal(final int[] touchXY, final Object dragInfo, final CellLayout cellLayout, 🔵
3626 final Runnable exitSpringLoadedRunnable = new Runnable() {
3627 @Override
3628 public void run() {
3629 mLauncher.exitSpringLoadedDragModeDelayed(true, Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIM🔵
3630 }
3631 };
3632 ItemInfo info = ((ItemInfo) (dragInfo));
3633 int spanX = info.spanX;
3634 int spanY = info.spanY;
3635 if (mDragInfo != null) {
3636 spanX = mDragInfo.spanX;
3637 spanY = mDragInfo.spanY;
3638 }
3639 final long container = (mLauncher.isHotseatLayout(cellLayout)) ? Favorites.CONTAINER_HOTSEAT : Fa🔵
3640 final long screenId = getIdForScreen(cellLayout);
3641 if (((!mLauncher.isHotseatLayout(cellLayout)) && (screenId != getScreenIdForPageIndex(mCurrentPag🔵
3642 snapToScreenId(screenId, null);
3643 }
3644 if (info instanceof PendingAddItemInfo) {
3645 final PendingAddItemInfo pendingInfo = ((PendingAddItemInfo) (dragInfo));
3646 boolean findNearestVacantCell = true;
3647 if (pendingInfo.itemType == Favorites.ITEM_TYPE_SHORTCUT) {
3648 mTargetCell = findNearestArea(((int) (touchXY[0])), ((int) (touchXY[1])), spanX, spanY, c🔵
3649 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisual🔵
3650 if (willCreateUserFolder(((ItemInfo) (d.dragInfo)), cellLayout, mTargetCell, distance, tr🔵
3651 findNearestVacantCell = false;
3652 }
3653 }
3654 final ItemInfo item = ((ItemInfo) (d.dragInfo));
3655 boolean updateWidgetSize = false;
3656 if (findNearestVacantCell) {
3657 int minSpanX = item.spanX;
3658 int minSpanY = item.spanY;
3659 if ((item.minSpanX > 0) && (item.minSpanY > 0)) {
3660 minSpanX = item.minSpanX;
3661 minSpanY = item.minSpanY;
3662 }
3663 int[] resultSpan = new int[2];
3664 mTargetCell = cellLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mDrag🔵
3665 if ((resultSpan[0] != item.spanX) || (resultSpan[1] != item.spanY)) {
3666 updateWidgetSize = true;
3667 }
3668 item.spanX = resultSpan[0];
3669 item.spanY = resultSpan[1];
3670 }
3671 Runnable onAnimationCompleteRunnable = new Runnable() {
3672 @Override
3673 public void run() {
3674 // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
3675 // adding an item that may not be dropped right away (due to a config activity)
3676 // we defer the removal until the activity returns.
3677 deferRemoveExtraEmptyScreen();
3678 // When dragging and dropping from customization tray, we deal with creating
3679 // widgets/shortcuts/folders in a slightly different way
3680 switch (pendingInfo.itemType) {
3681 case Favorites.ITEM_TYPE_APPWIDGET :
3682 int[] span = new int[2];
3683 span[0] = item.spanX;
3684 span[1] = item.spanY;
3685 mLauncher.addAppWidgetFromDrop(((PendingAddWidgetInfo) (pendingInfo)), contai🔵
3686 break;
3687 case Favorites.ITEM_TYPE_SHORTCUT :
3688 mLauncher.processShortcutFromDrop(pendingInfo.componentName, container, scree🔵
3689 break;
3690 default :
3691 throw new IllegalStateException("Unknown item type: " + pendingInfo.itemType)🔵
3692 }
3693 }
3694 };
3695 View finalView = (pendingInfo.itemType == Favorites.ITEM_TYPE_APPWIDGET) ? ((PendingAddWidget🔵
3696 if ((finalView instanceof AppWidgetHostView) && updateWidgetSize) {
3697 AppWidgetHostView awhv = ((AppWidgetHostView) (finalView));
3698 AppWidgetResizeFrame.updateWidgetSizeRanges(awhv, mLauncher, item.spanX, item.spanY);
3699 }
3700 int animationStyle = ANIMATE_INTO_POSITION_AND_DISAPPEAR;
3701 if ((pendingInfo.itemType == Favorites.ITEM_TYPE_APPWIDGET) && (((PendingAddWidgetInfo) (pend🔵
3702 animationStyle = ANIMATE_INTO_POSITION_AND_REMAIN;
3703 }
3704 animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable, animationStyle, 🔵
3705 } else {
3706 // This is for other drag/drop cases, like dragging from All Apps
3707 View view = null;
3708 switch (info.itemType) {
3709 case Favorites.ITEM_TYPE_APPLICATION :
3710 case Favorites.ITEM_TYPE_SHORTCUT :
3711 if ((info.container == NO_ID) && (info instanceof AppInfo)) {
3712 // Came from all apps -- make a copy
3713 info = new ShortcutInfo(((AppInfo) (info)));
3714 }
3715 view = mLauncher.createShortcut(R.layout.application, cellLayout, ((ShortcutInfo) (in🔵
3716 break;
3717 case Favorites.ITEM_TYPE_FOLDER :
3718 view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, ((FolderInfo) 🔵
3719 break;
3720 default :
3721 throw new IllegalStateException("Unknown item type: " + info.itemType);
3722 }
3723 // First we find the cell nearest to point at which the item is
3724 // dropped, without any consideration to whether there is an item there.
3725 if (touchXY != null) {
3726 mTargetCell = findNearestArea(((int) (touchXY[0])), ((int) (touchXY[1])), spanX, spanY, c🔵
3727 float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0], mDragViewVisual🔵
3728 d.postAnimationRunnable = exitSpringLoadedRunnable;
3729 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance, true,🔵
3730 return;
3731 }
3732 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d, true)) {
3733 return;
3734 }
3735 }
3736 if (touchXY != null) {
3737 // when dragging and dropping, just find the closest free spot
3738 mTargetCell = cellLayout.performReorder(((int) (mDragViewVisualCenter[0])), ((int) (mDrag🔵
3739 } else {
3740 cellLayout.findCellForSpan(mTargetCell, 1, 1);
3741 }
3742 // Add the item to DB before adding to screen ensures that the container and other
3743 // values of the info is properly updated.
3744 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId, mTargetCell[0], m🔵
3745 addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX, info.spanY🔵
3746 cellLayout.onDropChild(view);
3747 cellLayout.getShortcutsAndWidgets().measureChild(view);
3748 if (d.dragView != null) {
3749 // We wrap the animation call in the temporary set and reset of the current
3750 // cellLayout to its final transform -- this means we animate the drag view to
3751 // the correct final location.
3752 setFinalTransitionTransform(cellLayout);
3753 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, exitSpringLoadedRunnab🔵
3754 resetTransitionTransform(cellLayout);
3755 }
3756 }
3757 }
3758
3759 public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
3760 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(widgetInfo.spanX, widgetInfo.spanY🔵
3761 int visibility = layout.getVisibility();
3762 layout.setVisibility(VISIBLE);
3763 int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
3764 int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
3765 Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1], Bitmap.Config.ARGB_8888);
3766 mCanvas.setBitmap(b);
3767 layout.measure(width, height);
3768 layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
3769 layout.draw(mCanvas);
3770 mCanvas.setBitmap(null);
3771 layout.setVisibility(visibility);
3772 return b;
3773 }
3774
3775 private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
3776 DragView dragView, CellLayout layout, ItemInfo info, int[] targetCell,
3777 boolean external, boolean scale) {
3778 // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
3779 // location and size on the home screen.
3780 int spanX = info.spanX;
3781 int spanY = info.spanY;
3782
3783 Rect r = estimateItemPosition(layout, info, targetCell[0], targetCell[1], spanX, spanY);
3784 loc[0] = r.left;
3785 loc[1] = r.top;
3786
3787 setFinalTransitionTransform(layout);
3788 float cellLayoutScale =
3789 mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
3790 resetTransitionTransform(layout);
3791
3792 float dragViewScaleX;
3793 float dragViewScaleY;
3794 if (scale) {
3795 dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
3796 dragViewScaleY = (1.0f * r.height()) / dragView.getMeasuredHeight();
3797 } else {
3798 dragViewScaleX = 1f;
3799 dragViewScaleY = 1f;
3800 }
3801
3802 // The animation will scale the dragView about its center, so we need to center about
3803 // the final location.
3804 loc[0] -= (dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
3805 loc[1] -= (dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;
3806
3807 scaleXY[0] = dragViewScaleX * cellLayoutScale;
3808 scaleXY[1] = dragViewScaleY * cellLayoutScale;
3809 }
3810
3811 public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, DragView dragView,
3812 final Runnable onCompleteRunnable, int animationType, final View finalView,
3813 boolean external) {
3814 Rect from = new Rect();
3815 mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
3816
3817 int[] finalPos = new int[2];
3818 float scaleXY[] = new float[2];
3819 boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
3820 getFinalPositionForDropAnimation(finalPos, scaleXY, dragView, cellLayout, info, mTargetCell,
3821 external, scalePreview);
3822
3823 Resources res = mLauncher.getResources();
3824 final int duration = res.getInteger(R.integer.config_dropAnimMaxDuration) - 200;
3825
3826 // In the case where we've prebound the widget, we remove it from the DragLayer
3827 if (finalView instanceof AppWidgetHostView && external) {
3828 Log.d(TAG, "6557954 Animate widget drop, final view is appWidgetHostView");
3829 mLauncher.getDragLayer().removeView(finalView);
3830 }
3831 if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
3832 Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
3833 dragView.setCrossFadeBitmap(crossFadeBitmap);
3834 dragView.crossFade((int) (duration * 0.8f));
3835 } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET && external) {
3836 scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0], scaleXY[1]);
3837 }
3838
3839 DragLayer dragLayer = mLauncher.getDragLayer();
3840 if (animationType == CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION) {
3841 mLauncher.getDragLayer().animateViewIntoPosition(dragView, finalPos, 0f, 0.1f, 0.1f,
3842 DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
3843 } else {
3844 int endStyle;
3845 if (animationType == ANIMATE_INTO_POSITION_AND_REMAIN) {
3846 endStyle = DragLayer.ANIMATION_END_REMAIN_VISIBLE;
3847 } else {
3848 endStyle = DragLayer.ANIMATION_END_DISAPPEAR;;
3849 }
3850
3851 Runnable onComplete = new Runnable() {
3852 @Override
3853 public void run() {
3854 if (finalView != null) {
3855 finalView.setVisibility(VISIBLE);
3856 }
3857 if (onCompleteRunnable != null) {
3858 onCompleteRunnable.run();
3859 }
3860 }
3861 };
3862 dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
3863 finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
3864 duration, this);
3865 }
3866 }
3867
3868 public void setFinalTransitionTransform(CellLayout layout) {
3869 if (isSwitchingState()) {
3870 mCurrentScale = getScaleX();
3871 setScaleX(mNewScale);
3872 setScaleY(mNewScale);
3873 }
3874 }
3875
3876 public void resetTransitionTransform(CellLayout layout) {
3877 if (isSwitchingState()) {
3878 setScaleX(mCurrentScale);
3879 setScaleY(mCurrentScale);
3880 }
3881 }
3882
3883 /**
3884 * Return the current {@link CellLayout}, correctly picking the destination
3885 * screen while a scroll is in progress.
3886 */
3887 public CellLayout getCurrentDropLayout() {
3888 return (CellLayout) getChildAt(getNextPage());
3889 }
3890
3891 /**
3892 * Return the current CellInfo describing our current drag; this method exists
3893 * so that Launcher can sync this object with the correct info when the activity is created/
3894 * destroyed
3895 *
3896 */
3897 public CellLayout.CellInfo getDragInfo() {
3898 return mDragInfo;
3899 }
3900
3901 public int getCurrentPageOffsetFromCustomContent() {
3902 return getNextPage() - numCustomPages();
3903 }
3904
3905 /**
3906 * Calculate the nearest cell where the given object would be dropped.
3907 *
3908 * pixelX and pixelY should be in the coordinate system of layout
3909 */
3910 private int[] findNearestArea(int pixelX, int pixelY,
3911 int spanX, int spanY, CellLayout layout, int[] recycle) {
3912 return layout.findNearestArea(
3913 pixelX, pixelY, spanX, spanY, recycle);
3914 }
3915
3916 void setup(DragController dragController) {
3917 mSpringLoadedDragController = new SpringLoadedDragController(mLauncher);
3918 mDragController = dragController;
3919
3920 // hardware layers on children are enabled on startup, but should be disabled until
3921 // needed
3922 updateChildrenLayersEnabled(false);
3923 }
3924
3925 /**
3926 * Called at the end of a drag which originated on the workspace.
3927 */
3928 public void onDropCompleted(final View target, final DragObject d, final boolean isFlingToDelete, fin🔵
3929 if (mDeferDropAfterUninstall) {
3930 mDeferredAction = new Runnable() {
3931 public void run() {
3932 onDropCompleted(target, d, isFlingToDelete, success);
3933 mDeferredAction = null;
3934 }
3935 };
3936 return;
3937 }
3938 boolean beingCalledAfterUninstall = mDeferredAction != null;
3939 if (success && (!(beingCalledAfterUninstall && (!mUninstallSuccessful)))) {
3940 if ((target != this) && (mDragInfo != null)) {
3941 CellLayout parentCell = getParentCellLayoutForView(mDragInfo.cell);
3942 if (parentCell != null) {
3943 parentCell.removeView(mDragInfo.cell);
3944 } else if (LauncherAppState.isDogfoodBuild()) {
3945 throw new NullPointerException("mDragInfo.cell has null parent");
3946 }
3947 if (mDragInfo.cell instanceof DropTarget) {
3948 mDragController.removeDropTarget(((DropTarget) (mDragInfo.cell)));
3949 }
3950 }
3951 } else if (mDragInfo != null) {
3952 CellLayout cellLayout;
3953 if (mLauncher.isHotseatLayout(target)) {
3954 cellLayout = mLauncher.getHotseat().getLayout();
3955 } else {
3956 cellLayout = getScreenWithId(mDragInfo.screenId);
3957 }
3958 if ((cellLayout == null) && LauncherAppState.isDogfoodBuild()) {
3959 throw new RuntimeException("Invalid state: cellLayout == null in " + "Workspace#onDropCom🔵
3960 }
3961 if (cellLayout != null) {
3962 cellLayout.onDropChild(mDragInfo.cell);
3963 }
3964 }
3965 if ((d.cancelled || (beingCalledAfterUninstall && (!mUninstallSuccessful))) && (mDragInfo.cell !=🔵
3966 mDragInfo.cell.setVisibility(VISIBLE);
3967 }
3968 mDragOutline = null;
3969 mDragInfo = null;
3970 }
3971
3972 public void deferCompleteDropAfterUninstallActivity() {
3973 mDeferDropAfterUninstall = true;
3974 }
3975
3976 /// maybe move this into a smaller part
3977 public void onUninstallActivityReturned(boolean success) {
3978 mDeferDropAfterUninstall = false;
3979 mUninstallSuccessful = success;
3980 if (mDeferredAction != null) {
3981 mDeferredAction.run();
3982 }
3983 }
3984
3985 void updateItemLocationsInDatabase(CellLayout cl) {
3986 int count = cl.getShortcutsAndWidgets().getChildCount();
3987
3988 long screenId = getIdForScreen(cl);
3989 int container = Favorites.CONTAINER_DESKTOP;
3990
3991 if (mLauncher.isHotseatLayout(cl)) {
3992 screenId = -1;
3993 container = Favorites.CONTAINER_HOTSEAT;
3994 }
3995
3996 for (int i = 0; i < count; i++) {
3997 View v = cl.getShortcutsAndWidgets().getChildAt(i);
3998 ItemInfo info = (ItemInfo) v.getTag();
3999 // Null check required as the AllApps button doesn't have an item info
4000 if (info != null && info.requiresDbUpdate) {
4001 info.requiresDbUpdate = false;
4002 LauncherModel.modifyItemInDatabase(mLauncher, info, container, screenId, info.cellX,
4003 info.cellY, info.spanX, info.spanY);
4004 }
4005 }
4006 }
4007
4008 ArrayList<ComponentName> getUniqueComponents(boolean stripDuplicates, ArrayList<ComponentName> duplic🔵
4009 ArrayList<ComponentName> uniqueIntents = new ArrayList<ComponentName>();
4010 getUniqueIntents((CellLayout) mLauncher.getHotseat().getLayout(), uniqueIntents, duplicates, fals🔵
4011 int count = getChildCount();
4012 for (int i = 0; i < count; i++) {
4013 CellLayout cl = (CellLayout) getChildAt(i);
4014 getUniqueIntents(cl, uniqueIntents, duplicates, false);
4015 }
4016 return uniqueIntents;
4017 }
4018
4019 void getUniqueIntents(CellLayout cl, ArrayList<ComponentName> uniqueIntents,
4020 ArrayList<ComponentName> duplicates, boolean stripDuplicates) {
4021 int count = cl.getShortcutsAndWidgets().getChildCount();
4022
4023 ArrayList<View> children = new ArrayList<View>();
4024 for (int i = 0; i < count; i++) {
4025 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4026 children.add(v);
4027 }
4028
4029 for (int i = 0; i < count; i++) {
4030 View v = children.get(i);
4031 ItemInfo info = (ItemInfo) v.getTag();
4032 // Null check required as the AllApps button doesn't have an item info
4033 if (info instanceof ShortcutInfo) {
4034 ShortcutInfo si = (ShortcutInfo) info;
4035 ComponentName cn = si.intent.getComponent();
4036
4037 Uri dataUri = si.intent.getData();
4038 // If dataUri is not null / empty or if this component isn't one that would
4039 // have previously showed up in the AllApps list, then this is a widget-type
4040 // shortcut, so ignore it.
4041 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4042 continue;
4043 }
4044
4045 if (!uniqueIntents.contains(cn)) {
4046 uniqueIntents.add(cn);
4047 } else {
4048 if (stripDuplicates) {
4049 cl.removeViewInLayout(v);
4050 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4051 }
4052 if (duplicates != null) {
4053 duplicates.add(cn);
4054 }
4055 }
4056 }
4057 if (v instanceof FolderIcon) {
4058 FolderIcon fi = (FolderIcon) v;
4059 ArrayList<View> items = fi.getFolder().getItemsInReadingOrder();
4060 for (int j = 0; j < items.size(); j++) {
4061 if (items.get(j).getTag() instanceof ShortcutInfo) {
4062 ShortcutInfo si = (ShortcutInfo) items.get(j).getTag();
4063 ComponentName cn = si.intent.getComponent();
4064
4065 Uri dataUri = si.intent.getData();
4066 // If dataUri is not null / empty or if this component isn't one that would
4067 // have previously showed up in the AllApps list, then this is a widget-type
4068 // shortcut, so ignore it.
4069 if (dataUri != null && !dataUri.equals(Uri.EMPTY)) {
4070 continue;
4071 }
4072
4073 if (!uniqueIntents.contains(cn)) {
4074 uniqueIntents.add(cn);
4075 } else {
4076 if (stripDuplicates) {
4077 fi.getFolderInfo().remove(si);
4078 LauncherModel.deleteItemFromDatabase(mLauncher, si);
4079 }
4080 if (duplicates != null) {
4081 duplicates.add(cn);
4082 }
4083 }
4084 }
4085 }
4086 }
4087 }
4088 }
4089
4090 void saveWorkspaceToDb() {
4091 saveWorkspaceScreenToDb((CellLayout) mLauncher.getHotseat().getLayout());
4092 int count = getChildCount();
4093 for (int i = 0; i < count; i++) {
4094 CellLayout cl = (CellLayout) getChildAt(i);
4095 saveWorkspaceScreenToDb(cl);
4096 }
4097 }
4098
4099 void saveWorkspaceScreenToDb(CellLayout cl) {
4100 int count = cl.getShortcutsAndWidgets().getChildCount();
4101
4102 long screenId = getIdForScreen(cl);
4103 int container = Favorites.CONTAINER_DESKTOP;
4104
4105 Hotseat hotseat = mLauncher.getHotseat();
4106 if (mLauncher.isHotseatLayout(cl)) {
4107 screenId = -1;
4108 container = Favorites.CONTAINER_HOTSEAT;
4109 }
4110
4111 for (int i = 0; i < count; i++) {
4112 View v = cl.getShortcutsAndWidgets().getChildAt(i);
4113 ItemInfo info = (ItemInfo) v.getTag();
4114 // Null check required as the AllApps button doesn't have an item info
4115 if (info != null) {
4116 int cellX = info.cellX;
4117 int cellY = info.cellY;
4118 if (container == Favorites.CONTAINER_HOTSEAT) {
4119 cellX = hotseat.getCellXFromOrder((int) info.screenId);
4120 cellY = hotseat.getCellYFromOrder((int) info.screenId);
4121 }
4122 LauncherModel.addItemToDatabase(mLauncher, info, container, screenId, cellX,
4123 cellY, false);
4124 }
4125 if (v instanceof FolderIcon) {
4126 FolderIcon fi = (FolderIcon) v;
4127 fi.getFolder().addItemLocationsInDatabase();
4128 }
4129 }
4130 }
4131
4132 @Override
4133 public float getIntrinsicIconScaleFactor() {
4134 return 1f;
4135 }
4136
4137 @Override
4138 public boolean supportsFlingToDelete() {
4139 return true;
4140 }
4141
4142 @Override
4143 public boolean supportsAppInfoDropTarget() {
4144 return false;
4145 }
4146
4147 @Override
4148 public boolean supportsDeleteDropTarget() {
4149 return true;
4150 }
4151
4152 @Override
4153 public void onFlingToDelete(DragObject d, int x, int y, PointF vec) {
4154 // Do nothing
4155 }
4156
4157 @Override
4158 public void onFlingToDeleteCompleted() {
4159 // Do nothing
4160 }
4161
4162 public boolean isDropEnabled() {
4163 return true;
4164 }
4165
4166 @Override
4167 protected void onRestoreInstanceState(Parcelable state) {
4168 super.onRestoreInstanceState(state);
4169 Launcher.setScreen(mCurrentPage);
4170 }
4171
4172 @Override
4173 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
4174 // We don't dispatch restoreInstanceState to our children using this code path.
4175 // Some pages will be restored immediately as their items are bound immediately, and
4176 // others we will need to wait until after their items are bound.
4177 mSavedStates = container;
4178 }
4179
4180 public void restoreInstanceStateForChild(int child) {
4181 if (mSavedStates != null) {
4182 mRestoredPages.add(child);
4183 CellLayout cl = (CellLayout) getChildAt(child);
4184 if (cl != null) {
4185 cl.restoreInstanceState(mSavedStates);
4186 }
4187 }
4188 }
4189
4190 public void restoreInstanceStateForRemainingPages() {
4191 int count = getChildCount();
4192 for (int i = 0; i < count; i++) {
4193 if (!mRestoredPages.contains(i)) {
4194 restoreInstanceStateForChild(i);
4195 }
4196 }
4197 mRestoredPages.clear();
4198 mSavedStates = null;
4199 }
4200
4201 @Override
4202 public void scrollLeft() {
4203 if ((!workspaceInModalState()) && (!mIsSwitchingState)) {
4204 super.scrollLeft();
4205 }
4206 Folder openFolder = getOpenFolder();
4207 if (openFolder != null) {
4208 openFolder.completeDragExit();
4209 }
4210 }
4211
4212 @Override
4213 public void scrollRight() {
4214 if ((!workspaceInModalState()) && (!mIsSwitchingState)) {
4215 super.scrollRight();
4216 }
4217 Folder openFolder = getOpenFolder();
4218 if (openFolder != null) {
4219 openFolder.completeDragExit();
4220 }
4221 }
4222
4223 @Override
4224 public boolean onEnterScrollArea(int x, int y, int direction) {
4225 // Ignore the scroll area if we are dragging over the hot seat
4226 boolean isPortrait = !LauncherAppState.isScreenLandscape(getContext());
4227 if ((mLauncher.getHotseat() != null) && isPortrait) {
4228 Rect r = new Rect();
4229 mLauncher.getHotseat().getHitRect(r);
4230 if (r.contains(x, y)) {
4231 return false;
4232 }
4233 }
4234 boolean result = false;
4235 if (((!workspaceInModalState()) && (!mIsSwitchingState)) && (getOpenFolder() == null)) {
4236 mInScrollArea = true;
4237 final int page = getNextPage() + (direction == DragController.SCROLL_LEFT ? -1 : 1);
4238 // We always want to exit the current layout to ensure parity of enter / exit
4239 setCurrentDropLayout(null);
4240 if ((0 <= page) && (page < getChildCount())) {
4241 // Ensure that we are not dragging over to the custom content screen
4242 if (getScreenIdForPageIndex(page) == CUSTOM_CONTENT_SCREEN_ID) {
4243 return false;
4244 }
4245 CellLayout layout = ((CellLayout) (getChildAt(page)));
4246 setCurrentDragOverlappingLayout(layout);
4247 // Workspace is responsible for drawing the edge glow on adjacent pages,
4248 // so we need to redraw the workspace when this may have changed.
4249 invalidate();
4250 result = true;
4251 }
4252 }
4253 return result;
4254 }
4255
4256 @Override
4257 public boolean onExitScrollArea() {
4258 boolean result = false;
4259 if (mInScrollArea) {
4260 invalidate();
4261 CellLayout layout = getCurrentDropLayout();
4262 setCurrentDropLayout(layout);
4263 setCurrentDragOverlappingLayout(layout);
4264
4265 result = true;
4266 mInScrollArea = false;
4267 }
4268 return result;
4269 }
4270
4271 private void onResetScrollArea() {
4272 setCurrentDragOverlappingLayout(null);
4273 mInScrollArea = false;
4274 }
4275
4276 /**
4277 * Returns a specific CellLayout
4278 */
4279 CellLayout getParentCellLayoutForView(View v) {
4280 ArrayList<CellLayout> layouts = getWorkspaceAndHotseatCellLayouts();
4281 for (CellLayout layout : layouts) {
4282 if (layout.getShortcutsAndWidgets().indexOfChild(v) > -1) {
4283 return layout;
4284 }
4285 }
4286 return null;
4287 }
4288
4289 /**
4290 * Returns a list of all the CellLayouts in the workspace.
4291 */
4292 ArrayList<CellLayout> getWorkspaceAndHotseatCellLayouts() {
4293 ArrayList<CellLayout> layouts = new ArrayList<CellLayout>();
4294 int screenCount = getChildCount();
4295 for (int screen = 0; screen < screenCount; screen++) {
4296 layouts.add(((CellLayout) getChildAt(screen)));
4297 }
4298 if (mLauncher.getHotseat() != null) {
4299 layouts.add(mLauncher.getHotseat().getLayout());
4300 }
4301 return layouts;
4302 }
4303
4304 /**
4305 * We should only use this to search for specific children. Do not use this method to modify
4306 * ShortcutsAndWidgetsContainer directly. Includes ShortcutAndWidgetContainers from
4307 * the hotseat and workspace pages
4308 */
4309 ArrayList<ShortcutAndWidgetContainer> getAllShortcutAndWidgetContainers() {
4310 ArrayList<ShortcutAndWidgetContainer> childrenLayouts =
4311 new ArrayList<ShortcutAndWidgetContainer>();
4312 int screenCount = getChildCount();
4313 for (int screen = 0; screen < screenCount; screen++) {
4314 childrenLayouts.add(((CellLayout) getChildAt(screen)).getShortcutsAndWidgets());
4315 }
4316 if (mLauncher.getHotseat() != null) {
4317 childrenLayouts.add(mLauncher.getHotseat().getLayout().getShortcutsAndWidgets());
4318 }
4319 return childrenLayouts;
4320 }
4321
4322 public Folder getFolderForTag(final Object tag) {
4323 return ((Folder) (getFirstMatch(new ItemOperator() {
4324 @Override
4325 public boolean evaluate(ItemInfo info, View v, View parent) {
4326 return ((v instanceof Folder) && (((Folder) (v)).getInfo() == tag)) && ((Folder) (v)).get🔵
4327 }
4328 })));
4329 }
4330
4331 public View getViewForTag(final Object tag) {
4332 return getFirstMatch(new ItemOperator() {
4333
4334 @Override
4335 public boolean evaluate(ItemInfo info, View v, View parent) {
4336 return info == tag;
4337 }
4338 });
4339 }
4340
4341 public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
4342 return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() {
4343
4344 @Override
4345 public boolean evaluate(ItemInfo info, View v, View parent) {
4346 return (info instanceof LauncherAppWidgetInfo) &&
4347 ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId;
4348 }
4349 });
4350 }
4351
4352 private View getFirstMatch(final ItemOperator operator) {
4353 final View[] value = new View[1];
4354 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4355 @Override
4356 public boolean evaluate(ItemInfo info, View v, View parent) {
4357 if (operator.evaluate(info, v, parent)) {
4358 value[0] = v;
4359 return true;
4360 }
4361 return false;
4362 }
4363 });
4364 return value[0];
4365 }
4366
4367 void clearDropTargets() {
4368 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4369 @Override
4370 public boolean evaluate(ItemInfo info, View v, View parent) {
4371 if (v instanceof DropTarget) {
4372 mDragController.removeDropTarget(((DropTarget) (v)));
4373 }
4374 // not done, process all the shortcuts
4375 return false;
4376 }
4377 });
4378 }
4379
4380 // Removes ALL items that match a given package name, this is usually called when a package
4381 // has been removed and we want to remove all components (widgets, shortcuts, apps) that
4382 // belong to that package.
4383 void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
4384 final HashSet<String> packageNames = new HashSet<String>();
4385 packageNames.addAll(packages);
4386 // Filter out all the ItemInfos that this is going to affect
4387 final HashSet<ItemInfo> infos = new HashSet<ItemInfo>();
4388 final HashSet<ComponentName> cns = new HashSet<ComponentName>();
4389 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4390 for (CellLayout layoutParent : cellLayouts) {
4391 ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4392 int childCount = layout.getChildCount();
4393 for (int i = 0; i < childCount; ++i) {
4394 View view = layout.getChildAt(i);
4395 infos.add(((ItemInfo) (view.getTag())));
4396 }
4397 }
4398 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4399 @Override
4400 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
4401 if (packageNames.contains(cn.getPackageName()) && info.user.equals(user)) {
4402 cns.add(cn);
4403 return true;
4404 }
4405 return false;
4406 }
4407 };
4408 LauncherModel.filterItemInfos(infos, filter);
4409 // Remove the affected components
4410 removeItemsByComponentName(cns, user);
4411 }
4412
4413 // Removes items that match the application info specified, when applications are removed
4414 // as a part of an update, this is called to ensure that other widgets and application
4415 // shortcuts are not removed.
4416 void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos, UserHandleCompat user) {
4417 // Just create a hash table of all the specific components that this will affect
4418 HashSet<ComponentName> cns = new HashSet<ComponentName>();
4419 for (AppInfo info : appInfos) {
4420 cns.add(info.componentName);
4421 }
4422 // Remove all the things
4423 removeItemsByComponentName(cns, user);
4424 }
4425
4426 void removeItemsByComponentName(final HashSet<ComponentName> componentNames, final UserHandleCompat u🔵
4427 ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
4428 for (final CellLayout layoutParent : cellLayouts) {
4429 final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
4430 final HashMap<ItemInfo, View> children = new HashMap<ItemInfo, View>();
4431 for (int j = 0; j < layout.getChildCount(); j++) {
4432 final View view = layout.getChildAt(j);
4433 children.put(((ItemInfo) (view.getTag())), view);
4434 }
4435 final ArrayList<View> childrenToRemove = new ArrayList<View>();
4436 final HashMap<FolderInfo, ArrayList<ShortcutInfo>> folderAppsToRemove = new HashMap<FolderInf🔵
4437 LauncherModel.ItemInfoFilter filter = new LauncherModel.ItemInfoFilter() {
4438 @Override
4439 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
4440 if (parent instanceof FolderInfo) {
4441 if (componentNames.contains(cn) && info.user.equals(user)) {
4442 FolderInfo folder = ((FolderInfo) (parent));
4443 ArrayList<ShortcutInfo> appsToRemove;
4444 if (folderAppsToRemove.containsKey(folder)) {
4445 appsToRemove = folderAppsToRemove.get(folder);
4446 } else {
4447 appsToRemove = new ArrayList<ShortcutInfo>();
4448 folderAppsToRemove.put(folder, appsToRemove);
4449 }
4450 appsToRemove.add(((ShortcutInfo) (info)));
4451 return true;
4452 }
4453 } else if (componentNames.contains(cn) && info.user.equals(user)) {
4454 childrenToRemove.add(children.get(info));
4455 return true;
4456 }
4457 return false;
4458 }
4459 };
4460 LauncherModel.filterItemInfos(children.keySet(), filter);
4461 // Remove all the apps from their folders
4462 for (FolderInfo folder : folderAppsToRemove.keySet()) {
4463 ArrayList<ShortcutInfo> appsToRemove = folderAppsToRemove.get(folder);
4464 for (ShortcutInfo info : appsToRemove) {
4465 folder.remove(info);
4466 }
4467 }
4468 // Remove all the other children
4469 for (View child : childrenToRemove) {
4470 // Note: We can not remove the view directly from CellLayoutChildren as this
4471 // does not re-mark the spaces as unoccupied.
4472 layoutParent.removeViewInLayout(child);
4473 if (child instanceof DropTarget) {
4474 mDragController.removeDropTarget(((DropTarget) (child)));
4475 }
4476 }
4477 if (childrenToRemove.size() > 0) {
4478 layout.requestLayout();
4479 layout.invalidate();
4480 }
4481 }
4482 // Strip all the empty screens
4483 stripEmptyScreens();
4484 }
4485
4486 interface ItemOperator {
4487 /**
4488 * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}.
4489 *
4490 * @param info
4491 * info for the shortcut
4492 * @param view
4493 * view for the shortcut
4494 * @param parent
4495 * containing folder, or null
4496 * @return true if done, false to continue the map
4497 */
4498 public abstract boolean evaluate(ItemInfo info, View view, View parent);
4499 }
4500
4501 /**
4502 * Map the operator over the shortcuts and widgets, return the first-non-null value.
4503 *
4504 * @param recurse true: iterate over folder children. false: op get the folders themselves.
4505 * @param op the operator to map over the shortcuts
4506 */
4507 void mapOverItems(boolean recurse, ItemOperator op) {
4508 ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers();
4509 final int containerCount = containers.size();
4510 for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) {
4511 ShortcutAndWidgetContainer container = containers.get(containerIdx);
4512 // map over all the shortcuts on the workspace
4513 final int itemCount = container.getChildCount();
4514 for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
4515 View item = container.getChildAt(itemIdx);
4516 ItemInfo info = ((ItemInfo) (item.getTag()));
4517 if ((recurse && (info instanceof FolderInfo)) && (item instanceof FolderIcon)) {
4518 FolderIcon folder = ((FolderIcon) (item));
4519 ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder();
4520 // map over all the children in the folder
4521 final int childCount = folderChildren.size();
4522 for (int childIdx = 0; childIdx < childCount; childIdx++) {
4523 View child = folderChildren.get(childIdx);
4524 info = ((ItemInfo) (child.getTag()));
4525 if (op.evaluate(info, child, folder)) {
4526 return;
4527 }
4528 }
4529 } else if (op.evaluate(info, item, null)) {
4530 return;
4531 }
4532 }
4533 }
4534 }
4535
4536 void updateShortcutsAndWidgets(ArrayList<AppInfo> apps) {
4537 // Break the appinfo list per user
4538 final HashMap<UserHandleCompat, ArrayList<AppInfo>> appsPerUser = new HashMap<UserHandleCompat, A🔵
4539 for (AppInfo info : apps) {
4540 ArrayList<AppInfo> filtered = appsPerUser.get(info.user);
4541 if (filtered == null) {
4542 filtered = new ArrayList<AppInfo>();
4543 appsPerUser.put(info.user, filtered);
4544 }
4545 filtered.add(info);
4546 }
4547 for (Map.Entry<UserHandleCompat, ArrayList<AppInfo>> entry : appsPerUser.entrySet()) {
4548 updateShortcutsAndWidgetsPerUser(entry.getValue(), entry.getKey());
4549 }
4550 }
4551
4552 private void updateShortcutsAndWidgetsPerUser(ArrayList<AppInfo> apps, final UserHandleCompat user) {
4553 // Create a map of the apps to test against
4554 final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>();
4555 final HashSet<String> pkgNames = new HashSet<String>();
4556 for (AppInfo ai : apps) {
4557 appsMap.put(ai.componentName, ai);
4558 pkgNames.add(ai.componentName.getPackageName());
4559 }
4560 final HashSet<ComponentName> iconsToRemove = new HashSet<ComponentName>();
4561 mapOverItems(MAP_RECURSE, new ItemOperator() {
4562 @Override
4563 public boolean evaluate(ItemInfo info, View v, View parent) {
4564 if ((info instanceof ShortcutInfo) && (v instanceof BubbleTextView)) {
4565 ShortcutInfo shortcutInfo = ((ShortcutInfo) (info));
4566 ComponentName cn = shortcutInfo.getTargetComponent();
4567 AppInfo appInfo = appsMap.get(cn);
4568 if (((user.equals(shortcutInfo.user) && (cn != null)) && LauncherModel.isShortcutInfo🔵
4569 boolean promiseStateChanged = false;
4570 boolean infoUpdated = false;
4571 if (shortcutInfo.isPromise()) {
4572 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4573 // Auto install icon
4574 PackageManager pm = getContext().getPackageManager();
4575 ResolveInfo matched = pm.resolveActivity(new Intent(Intent.ACTION_MAIN).s🔵
4576 if (matched == null) {
4577 // Try to find the best match activity.
4578 Intent intent = pm.getLaunchIntentForPackage(cn.getPackageName());
4579 if (intent != null) {
4580 cn = intent.getComponent();
4581 appInfo = appsMap.get(cn);
4582 }
4583 if ((intent == null) || (appsMap == null)) {
4584 // Could not find a default activity. Remove this item.
4585 iconsToRemove.add(shortcutInfo.getTargetComponent());
4586 // process next shortcut.
4587 return false;
4588 }
4589 shortcutInfo.promisedIntent = intent;
4590 }
4591 }
4592 // Restore the shortcut.
4593 shortcutInfo.intent = shortcutInfo.promisedIntent;
4594 shortcutInfo.promisedIntent = null;
4595 shortcutInfo.status &= ((~ShortcutInfo.FLAG_RESTORED_ICON) & (~ShortcutInfo.F🔵
4596 promiseStateChanged = true;
4597 infoUpdated = true;
4598 shortcutInfo.updateIcon(mIconCache);
4599 LauncherModel.updateItemInDatabase(getContext(), shortcutInfo);
4600 }
4601 if (appInfo != null) {
4602 shortcutInfo.updateIcon(mIconCache);
4603 shortcutInfo.title = appInfo.title.toString();
4604 shortcutInfo.contentDescription = appInfo.contentDescription;
4605 infoUpdated = true;
4606 }
4607 if (infoUpdated) {
4608 BubbleTextView shortcut = ((BubbleTextView) (v));
4609 shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, promiseStateCh🔵
4610 if (parent != null) {
4611 parent.invalidate();
4612 }
4613 }
4614 }
4615 }
4616 // process all the shortcuts
4617 return false;
4618 }
4619 });
4620 if (!iconsToRemove.isEmpty()) {
4621 removeItemsByComponentName(iconsToRemove, user);
4622 }
4623 if (user.equals(UserHandleCompat.myUserHandle())) {
4624 restorePendingWidgets(pkgNames);
4625 }
4626 }
4627
4628 public void removeAbandonedPromise(String packageName, UserHandleCompat user) {
4629 ArrayList<String> packages = new ArrayList<String>(1);
4630 packages.add(packageName);
4631 LauncherModel.deletePackageFromDatabase(mLauncher, packageName, user);
4632 removeItemsByPackageName(packages, user);
4633 }
4634
4635 public void updatePackageBadge(final String packageName, final UserHandleCompat user) {
4636 mapOverItems(MAP_RECURSE, new ItemOperator() {
4637 @Override
4638 public boolean evaluate(ItemInfo info, View v, View parent) {
4639 if ((info instanceof ShortcutInfo) && (v instanceof BubbleTextView)) {
4640 ShortcutInfo shortcutInfo = ((ShortcutInfo) (info));
4641 ComponentName cn = shortcutInfo.getTargetComponent();
4642 if (((user.equals(shortcutInfo.user) && (cn != null)) && shortcutInfo.isPromise()) &&🔵
4643 if (shortcutInfo.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
4644 // For auto install apps update the icon as well as label.
4645 mIconCache.getTitleAndIcon(shortcutInfo, shortcutInfo.promisedIntent, user, t🔵
4646 } else {
4647 // Only update the icon for restored apps.
4648 shortcutInfo.updateIcon(mIconCache);
4649 }
4650 BubbleTextView shortcut = ((BubbleTextView) (v));
4651 shortcut.applyFromShortcutInfo(shortcutInfo, mIconCache, true, false);
4652 if (parent != null) {
4653 parent.invalidate();
4654 }
4655 }
4656 }
4657 // process all the shortcuts
4658 return false;
4659 }
4660 });
4661 }
4662
4663 public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {
4664 HashSet<String> completedPackages = new HashSet<String>();
4665 for (final PackageInstallInfo installInfo : installInfos) {
4666 mapOverItems(MAP_RECURSE, new ItemOperator() {
4667 @Override
4668 public boolean evaluate(ItemInfo info, View v, View parent) {
4669 if ((info instanceof ShortcutInfo) && (v instanceof BubbleTextView)) {
4670 ShortcutInfo si = ((ShortcutInfo) (info));
4671 ComponentName cn = si.getTargetComponent();
4672 if ((si.isPromise() && (cn != null)) && installInfo.packageName.equals(cn.getPack🔵
4673 si.setInstallProgress(installInfo.progress);
4674 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
4675 // Mark this info as broken.
4676 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
4677 }
4678 ((BubbleTextView) (v)).applyState(false);
4679 }
4680 } else if (((v instanceof PendingAppWidgetHostView) && (info instanceof LauncherAppWi🔵
4681 ((LauncherAppWidgetInfo) (info)).installProgress = installInfo.progress;
4682 ((PendingAppWidgetHostView) (v)).applyState();
4683 }
4684 // process all the shortcuts
4685 return false;
4686 }
4687 });
4688 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
4689 completedPackages.add(installInfo.packageName);
4690 }
4691 }
4692 // Note that package states are sent only for myUser
4693 if (!completedPackages.isEmpty()) {
4694 restorePendingWidgets(completedPackages);
4695 }
4696 }
4697
4698 private void restorePendingWidgets(final Set<String> installedPackaged) {
4699 final ArrayList<LauncherAppWidgetInfo> changedInfo = new ArrayList<LauncherAppWidgetInfo>();
4700 // Iterate non recursively as widgets can't be inside a folder.
4701 mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
4702 @Override
4703 public boolean evaluate(ItemInfo info, View v, View parent) {
4704 if (info instanceof LauncherAppWidgetInfo) {
4705 LauncherAppWidgetInfo widgetInfo = ((LauncherAppWidgetInfo) (info));
4706 if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && insta🔵
4707 changedInfo.add(widgetInfo);
4708 // Remove the provider not ready flag
4709 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
4710 LauncherModel.updateItemInDatabase(getContext(), widgetInfo);
4711 }
4712 }
4713 // process all the widget
4714 return false;
4715 }
4716 });
4717 if (!changedInfo.isEmpty()) {
4718 DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, mLauncher.getApp🔵
4719 if (LauncherModel.findAppWidgetProviderInfoWithComponent(getContext(), changedInfo.get(0).pro🔵
4720 // Re-inflate the widgets which have changed status
4721 widgetRefresh.run();
4722 } else {
4723 // widgetRefresh will automatically run when the packages are updated.
4724 }
4725 }
4726 }
4727
4728 private void moveToScreen(int page, boolean animate) {
4729 if (!workspaceInModalState()) {
4730 if (animate) {
4731 snapToPage(page);
4732 } else {
4733 setCurrentPage(page);
4734 }
4735 }
4736 View child = getChildAt(page);
4737 if (child != null) {
4738 child.requestFocus();
4739 }
4740 }
4741
4742 void moveToDefaultScreen(boolean animate) {
4743 moveToScreen(mDefaultPage, animate);
4744 }
4745
4746 void moveToCustomContentScreen(boolean animate) {
4747 if (hasCustomContent()) {
4748 int ccIndex = getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID);
4749 if (animate) {
4750 snapToPage(ccIndex);
4751 } else {
4752 setCurrentPage(ccIndex);
4753 }
4754 View child = getChildAt(ccIndex);
4755 if (child != null) {
4756 child.requestFocus();
4757 }
4758 }
4759 exitWidgetResizeMode();
4760 }
4761
4762 @Override
4763 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
4764 long screenId = getScreenIdForPageIndex(pageIndex);
4765 if (screenId == EXTRA_EMPTY_SCREEN_ID) {
4766 int count = mScreenOrder.size() - numCustomPages();
4767 if (count > 1) {
4768 return new PageIndicator.PageMarkerResources(R.drawable.ic_pageindicator_current,
4769 R.drawable.ic_pageindicator_add);
4770 }
4771 }
4772
4773 return super.getPageIndicatorMarker(pageIndex);
4774 }
4775
4776 @Override
4777 public void syncPages() {
4778 }
4779
4780 @Override
4781 public void syncPageItems(int page, boolean immediate) {
4782 }
4783
4784 protected String getPageIndicatorDescription() {
4785 String settings = getResources().getString(R.string.settings_button_text);
4786 return getCurrentPageDescription() + ", " + settings;
4787 }
4788
4789 protected String getCurrentPageDescription() {
4790 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
4791 int delta = numCustomPages();
4792 if (hasCustomContent() && getNextPage() == 0) {
4793 return mCustomContentDescription;
4794 }
4795 return String.format(getContext().getString(R.string.workspace_scroll_format),
4796 page + 1 - delta, getChildCount() - delta);
4797 }
4798
4799 public void getLocationInDragLayer(int[] loc) {
4800 mLauncher.getDragLayer().getLocationInDragLayer(this, loc);
4801 }
4802
4803 /**
4804 * Used as a workaround to ensure that the AppWidgetService receives the
4805 * PACKAGE_ADDED broadcast before updating widgets.
4806 */
4807 private class DeferredWidgetRefresh implements Runnable {
4808 private final ArrayList<LauncherAppWidgetInfo> mInfos;
4809
4810 private final LauncherAppWidgetHost mHost;
4811
4812 private final Handler mHandler;
4813
4814 private boolean mRefreshPending;
4815
4816 public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos, LauncherAppWidgetHost host) 🔵
4817 mInfos = infos;
4818 mHost = host;
4819 mHandler = new Handler();
4820 mRefreshPending = true;
4821 mHost.addProviderChangeListener(this);
4822 // Force refresh after 10 seconds, if we don't get the provider changed event.
4823 // This could happen when the provider is no longer available in the app.
4824 mHandler.postDelayed(this, 10000);
4825 }
4826
4827 @Override
4828 public void run() {
4829 mHost.removeProviderChangeListener(this);
4830 mHandler.removeCallbacks(this);
4831
4832 if (!mRefreshPending) {
4833 return;
4834 }
4835
4836 mRefreshPending = false;
4837
4838 for (LauncherAppWidgetInfo info : mInfos) {
4839 if (info.hostView instanceof PendingAppWidgetHostView) {
4840 PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView;
4841 mLauncher.removeAppWidget(info);
4842
4843 CellLayout cl = (CellLayout) view.getParent().getParent();
4844 // Remove the current widget
4845 cl.removeView(view);
4846 mLauncher.bindAppWidget(info);
4847 }
4848 }
4849 }
4850 }
4851 }
|